##// END OF EJS Templates
Implemented generation of changesets based...
marcink -
r2995:32471bd1 beta
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

This diff has been collapsed as it changes many lines, (570 lines changed) Show them Hide them
@@ -0,0 +1,570 b''
1 diff --git a/img/baseline-10px.png b/img/baseline-10px.png
2 new file mode 100644
3 index 0000000000000000000000000000000000000000..16095dcbf5c9ea41caeb1e3e41d647d425222ed1
4 GIT binary patch
5 literal 152
6 zcmeAS@N?(olHy`uVBq!ia0vp^j6j^i!3HGVb)pi0lw^r(L`iUdT1k0gQ7VIDN`6wR
7 zf@f}GdTLN=VoGJ<$y6JlA}dc9$B>F!Nx%O8w`Ue+77%bXFxq5j_~-xsZV_1~1zCBH
8 y)y@U((_~Lrb!=|_@`K?vV_&A58+!u-Gs6x+MGjBnI|qTLFnGH9xvX<aXaWHBd@WW0
9
10 literal 0
11 HcmV?d00001
12
13 diff --git a/img/baseline-20px.png b/img/baseline-20px.png
14 deleted file mode 100644
15 index 6894c4c8289e8600b595caf70d481324e640b8bd..0000000000000000000000000000000000000000
16 GIT binary patch
17 literal 0
18 HcmV?d00001
19
20 literal 127
21 zcmeAS@N?(olHy`uVBq!ia0vp^j6j^i!3HGVb)pi0lw^r(L`iUdT1k0gQ7VIDN`6wR
22 zf@f}GdTLN=VoGJ<$y6JlB1KOZ$B>F!Nk9Jow`XSN<z!=HoHesY!R@WWAwG@Mj0y)B
23 YKKcpQXiYX%0xD+kboFyt=akR{0IF0Y6aWAK
24
25 diff --git a/index.html b/index.html
26 index 9aa2355b0d5f3dd324c8191637703c944ca14fca..654d5cc338e5cb27b41e09739363b025242d8acf 100644
27 --- a/index.html
28 +++ b/index.html
29 @@ -2,7 +2,7 @@
30 <html lang="en">
31 <head>
32 <meta charset="utf-8">
33 - <title>Baseline</title>
34 + <title>Twitter Baseline</title>
35
36 <!-- // Less.js at the ready! -->
37 <link rel="stylesheet/less" type="text/css" media="all" href="less/baseline.less" />
38 @@ -11,6 +11,7 @@
39 <!-- // jQuery! -->
40 <script type="text/javascript" src="http://code.jquery.com/jquery-1.5.2.min.js"></script>
41 <script type="text/javascript" src="http://tablesorter.com/jquery.tablesorter.min.js"></script>
42 + <script type="text/javascript" src="js/jquery/hashgrid.js"></script>
43 <script type="text/javascript">
44 $(document).ready(function(){
45 // Active state in top nav
46 @@ -36,7 +37,7 @@
47 <!--
48 <style>
49 body {
50 - background: url(img/baseline-20px.png) repeat 0 0, url(img/grid-940px.png) repeat-y top center;
51 + background: url(img/baseline-10px.png) repeat 0 0, url(img/grid-940px.png) repeat-y top center;
52 background-color: #fff;
53 }
54 </style>
55 diff --git a/js/global.js b/js/global.js
56 deleted file mode 100644
57 index a934a3360ef6bdf340a036e6f3475daa7a54103c..0000000000000000000000000000000000000000
58 --- a/js/global.js
59 +++ /dev/null
60 @@ -1,75 +0,0 @@
61 -$(document).ready(function(){
62 - // Get Heights
63 - windowHeight = $(window).height();
64 - documentHeight = $(document).height();
65 - sidebarHeight = windowHeight - 40;
66 - containerHeight = windowHeight - 40;
67 -
68 - // Get Widths
69 - windowWidth = $(window).width();
70 - containerWidth = windowWidth - 200;
71 -
72 - if (windowHeight < containerHeight) {
73 -
74 - // Set Dimensions for default state (before resize)
75 - $('div#sidebar').css({
76 - height: sidebarHeight
77 - });
78 - $('div#container').css({
79 - width: containerWidth,
80 - height: containerHeight
81 - });
82 -
83 - } else {
84 -
85 - // During resize, set widths
86 - $(window).resize(function() {
87 - console.log('Window Height: ' + $(window).height() + ', Sidebar Height:' + ($(window).height() - 40));
88 -
89 - // Get Heights
90 - windowHeight = $(window).height();
91 - sidebarHeight = windowHeight - 40;
92 - containerHeight = windowHeight - 40;
93 -
94 - // Get Widths
95 - windowWidth = $(window).width();
96 - containerWidth = windowWidth - 200;
97 -
98 - // Set Dimensions for default state (before resize)
99 - $('div#sidebar').css({
100 - height: sidebarHeight
101 - });
102 - $('div#container').css({
103 - width: containerWidth,
104 - height: containerHeight
105 - });
106 - });
107 - // console.log('omgz window is less than container so... fuck.');
108 - $('div#sidebar').css({
109 - height: documentHeight - 40
110 - });
111 -
112 - }
113 -
114 -
115 -
116 -/*
117 - // Toggle Calendars
118 - $('div#sidebar ul li a').click(function() {
119 - if ($(this).is('#toggleMonthView')) {
120 - console.log('toggle month');
121 - $(this).addClass('active');
122 - $('#toggleListView').removeClass('active');
123 - $('table#monthView').show();
124 - $('table#listView').hide();
125 - } else {
126 - console.log('toggle list');
127 - $(this).addClass('active');
128 - $('#toggleMonthView').removeClass('active');
129 - $('table#listView').show();
130 - $('table#monthView').hide();
131 - }
132 - return false;
133 - });
134 -*/
135 -});
136 diff --git a/js/jquery/hashgrid.js b/js/jquery/hashgrid.js
137 new file mode 100755
138 index 0000000000000000000000000000000000000000..db7af7989499dd359737a880828f8d1b08571f81
139 --- /dev/null
140 +++ b/js/jquery/hashgrid.js
141 @@ -0,0 +1,340 @@
142 +/**
143 + * hashgrid (jQuery version)
144 + * http://github.com/dotjay/hashgrid
145 + * Version 5, 3 Nov 2010
146 + * Written by Jon Gibbins, dotjay.co.uk, accessibility.co.uk
147 + * Contibutors:
148 + * Sean Coates, seancoates.com
149 + * Phil Dokas, jetless.org
150 + *
151 + * // Using a basic #grid setup
152 + * var grid = new hashgrid();
153 + *
154 + * // Using #grid with a custom id (e.g. #mygrid)
155 + * var grid = new hashgrid("mygrid");
156 + *
157 + * // Using #grid with additional options
158 + * var grid = new hashgrid({
159 + * id: 'mygrid', // id for the grid container
160 + * modifierKey: 'alt', // optional 'ctrl', 'alt' or 'shift'
161 + * showGridKey: 's', // key to show the grid
162 + * holdGridKey: 'enter', // key to hold the grid in place
163 + * foregroundKey: 'f', // key to toggle foreground/background
164 + * jumpGridsKey: 'd', // key to cycle through the grid classes
165 + * numberOfGrids: 2, // number of grid classes used
166 + * classPrefix: 'class', // prefix for the grid classes
167 + * cookiePrefix: 'mygrid' // prefix for the cookie name
168 + * });
169 + */
170 +if (typeof jQuery == "undefined") {
171 + alert("Hashgrid: jQuery not loaded. Make sure it's linked to your pages.");
172 +}
173 +
174 +
175 +/**
176 + * hashgrid overlay
177 + */
178 +var hashgrid = function(set) {
179 +
180 + var options = {
181 + id: 'grid', // id for the grid container
182 + modifierKey: null, // optional 'ctrl', 'alt' or 'shift'
183 + showGridKey: 'g', // key to show the grid
184 + holdGridKey: 'h', // key to hold the grid in place
185 + foregroundKey: 'f', // key to toggle foreground/background
186 + jumpGridsKey: 'j', // key to cycle through the grid classes
187 + numberOfGrids: 1, // number of grid classes used
188 + classPrefix: 'grid-', // prefix for the grid classes
189 + cookiePrefix: 'hashgrid'// prefix for the cookie name
190 + };
191 + var overlayOn = false,
192 + sticky = false,
193 + overlayZState = 'B',
194 + overlayZBackground = -1,
195 + overlayZForeground = 9999,
196 + classNumber = 1;
197 +
198 + // Apply options
199 + if (typeof set == 'object') {
200 + var k;
201 + for (k in set) options[k] = set[k];
202 + }
203 + else if (typeof set == 'string') {
204 + options.id = set;
205 + }
206 +
207 + // Remove any conflicting overlay
208 + if ($('#' + options.id).length > 0) {
209 + $('#' + options.id).remove();
210 + }
211 +
212 + // Create overlay, hidden before adding to DOM
213 + var overlayEl = $('<div></div>');
214 + overlayEl
215 + .attr('id', options.id)
216 + .css({
217 + display: 'none',
218 + 'pointer-events': 'none'
219 + });
220 + $("body").prepend(overlayEl);
221 + var overlay = $('#' + options.id);
222 +
223 + // Unless a custom z-index is set, ensure the overlay will be behind everything
224 + if (overlay.css('z-index') == 'auto') overlay.css('z-index', overlayZBackground);
225 +
226 + // Override the default overlay height with the actual page height
227 + var pageHeight = parseFloat($(document).height());
228 + overlay.height(pageHeight);
229 +
230 + // Add the first grid line so that we can measure it
231 + overlay.append('<div id="' + options.id + '-horiz" class="horiz first-line">');
232 +
233 + // Position off-screen and display to calculate height
234 + var top = overlay.css("top");
235 + overlay.css({
236 + top: "-999px",
237 + display: "block"
238 + });
239 +
240 + // Calculate the number of grid lines needed
241 + var line = $('#' + options.id + '-horiz'),
242 + lineHeight = line.outerHeight();
243 +
244 + // Hide and reset top
245 + overlay.css({
246 + display: "none",
247 + top: top
248 + });
249 +
250 + // Break on zero line height
251 + if (lineHeight <= 0) return true;
252 +
253 + // Add the remaining grid lines
254 + var i, numGridLines = Math.floor(pageHeight / lineHeight);
255 + for (i = numGridLines - 1; i >= 1; i--) {
256 + overlay.append('<div class="horiz"></div>');
257 + }
258 +
259 + // vertical grid
260 + overlay.append($('<div class="vert-container"></div>'));
261 + var overlayVert = overlay.children('.vert-container');
262 + var gridWidth = overlay.width();
263 + overlayVert.css({width: gridWidth, position: 'absolute', top: 0});
264 + overlayVert.append('<div class="vert first-line">&nbsp;</div>');
265 +
266 + // 30 is an arbitrarily large number...
267 + // can't calculate the margin width properly
268 + for (i = 0; i < 30; i++) {
269 + overlayVert.append('<div class="vert">&nbsp;</div>');
270 + }
271 +
272 + overlayVert.children()
273 + .height(pageHeight)
274 + .css({display: 'inline-block'});
275 +
276 + // Check for saved state
277 + var overlayCookie = readCookie(options.cookiePrefix + options.id);
278 + if (typeof overlayCookie == 'string') {
279 + var state = overlayCookie.split(',');
280 + state[2] = Number(state[2]);
281 + if ((typeof state[2] == 'number') && !isNaN(state[2])) {
282 + classNumber = state[2].toFixed(0);
283 + overlay.addClass(options.classPrefix + classNumber);
284 + }
285 + if (state[1] == 'F') {
286 + overlayZState = 'F';
287 + overlay.css('z-index', overlayZForeground);
288 + }
289 + if (state[0] == '1') {
290 + overlayOn = true;
291 + sticky = true;
292 + showOverlay();
293 + }
294 + }
295 + else {
296 + overlay.addClass(options.classPrefix + classNumber);
297 + }
298 +
299 + // Keyboard controls
300 + $(document).bind('keydown', keydownHandler);
301 + $(document).bind('keyup', keyupHandler);
302 +
303 + /**
304 + * Helpers
305 + */
306 +
307 + function getModifier(e) {
308 + if (options.modifierKey == null) return true; // Bypass by default
309 + var m = true;
310 + switch(options.modifierKey) {
311 + case 'ctrl':
312 + m = (e.ctrlKey ? e.ctrlKey : false);
313 + break;
314 +
315 + case 'alt':
316 + m = (e.altKey ? e.altKey : false);
317 + break;
318 +
319 + case 'shift':
320 + m = (e.shiftKey ? e.shiftKey : false);
321 + break;
322 + }
323 + return m;
324 + }
325 +
326 + function getKey(e) {
327 + var k = false, c = (e.keyCode ? e.keyCode : e.which);
328 + // Handle keywords
329 + if (c == 13) k = 'enter';
330 + // Handle letters
331 + else k = String.fromCharCode(c).toLowerCase();
332 + return k;
333 + }
334 +
335 + function saveState() {
336 + createCookie(options.cookiePrefix + options.id, (sticky ? '1' : '0') + ',' + overlayZState + ',' + classNumber, 1);
337 + }
338 +
339 + function showOverlay() {
340 + overlay.show();
341 + overlayVert.css({width: overlay.width()});
342 + // hide any vertical blocks that aren't at the top of the viewport
343 + overlayVert.children('.vert').each(function () {
344 + $(this).css('display','inline-block');
345 + if ($(this).offset().top > 0) {
346 + $(this).hide();
347 + }
348 + });
349 + }
350 +
351 + /**
352 + * Event handlers
353 + */
354 +
355 + function keydownHandler(e) {
356 + var source = e.target.tagName.toLowerCase();
357 + if ((source == 'input') || (source == 'textarea') || (source == 'select')) return true;
358 + var m = getModifier(e);
359 + if (!m) return true;
360 + var k = getKey(e);
361 + if (!k) return true;
362 + switch(k) {
363 + case options.showGridKey:
364 + if (!overlayOn) {
365 + showOverlay();
366 + overlayOn = true;
367 + }
368 + else if (sticky) {
369 + overlay.hide();
370 + overlayOn = false;
371 + sticky = false;
372 + saveState();
373 + }
374 + break;
375 + case options.holdGridKey:
376 + if (overlayOn && !sticky) {
377 + // Turn sticky overlay on
378 + sticky = true;
379 + saveState();
380 + }
381 + break;
382 + case options.foregroundKey:
383 + if (overlayOn) {
384 + // Toggle sticky overlay z-index
385 + if (overlay.css('z-index') == overlayZForeground) {
386 + overlay.css('z-index', overlayZBackground);
387 + overlayZState = 'B';
388 + }
389 + else {
390 + overlay.css('z-index', overlayZForeground);
391 + overlayZState = 'F';
392 + }
393 + saveState();
394 + }
395 + break;
396 + case options.jumpGridsKey:
397 + if (overlayOn && (options.numberOfGrids > 1)) {
398 + // Cycle through the available grids
399 + overlay.removeClass(options.classPrefix + classNumber);
400 + classNumber++;
401 + if (classNumber > options.numberOfGrids) classNumber = 1;
402 + overlay.addClass(options.classPrefix + classNumber);
403 + showOverlay();
404 + if (/webkit/.test( navigator.userAgent.toLowerCase() )) {
405 + forceRepaint();
406 + }
407 + saveState();
408 + }
409 + break;
410 + }
411 + }
412 +
413 + function keyupHandler(e) {
414 + var m = getModifier(e);
415 + if (!m) return true;
416 + var k = getKey(e);
417 + if (!k) return true;
418 + if ((k == options.showGridKey) && !sticky) {
419 + overlay.hide();
420 + overlayOn = false;
421 + }
422 + }
423 +
424 + /**
425 + * Cookie functions
426 + *
427 + * By Peter-Paul Koch:
428 + * http://www.quirksmode.org/js/cookies.html
429 + */
430 + function createCookie(name,value,days) {
431 + if (days) {
432 + var date = new Date();
433 + date.setTime(date.getTime()+(days*24*60*60*1000));
434 + var expires = "; expires="+date.toGMTString();
435 + }
436 + else var expires = "";
437 + document.cookie = name+"="+value+expires+"; path=/";
438 + }
439 +
440 + function readCookie(name) {
441 + var nameEQ = name + "=";
442 + var ca = document.cookie.split(';');
443 + for(var i=0;i < ca.length;i++) {
444 + var c = ca[i];
445 + while (c.charAt(0)==' ') c = c.substring(1,c.length);
446 + if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
447 + }
448 + return null;
449 + }
450 +
451 + function eraseCookie(name) {
452 + createCookie(name,"",-1);
453 + }
454 +
455 + /**
456 + * Forces a repaint (because WebKit has issues)
457 + * http://www.sitepoint.com/forums/showthread.php?p=4538763
458 + * http://www.phpied.com/the-new-game-show-will-it-reflow/
459 + */
460 + function forceRepaint() {
461 + var ss = document.styleSheets[0];
462 + try {
463 + ss.addRule('.xxxxxx', 'position: relative');
464 + ss.removeRule(ss.rules.length - 1);
465 + } catch(e){}
466 + }
467 +
468 +}
469 +
470 +
471 +/**
472 + * You can call hashgrid from your own code, but it's loaded here as
473 + * an example for your convenience.
474 + */
475 +$(document).ready(function() {
476 +
477 + var grid = new hashgrid({
478 + numberOfGrids: 2
479 + });
480 +
481 +});
482 diff --git a/less/docs.less b/less/docs.less
483 index 165ddb142019d6ec45aae1fa3fdbb3cbcea3bfdb..30b561118c290925a213d987691dfcbeafeb8f44 100644
484 --- a/less/docs.less
485 +++ b/less/docs.less
486 @@ -1,3 +1,10 @@
487 +body {
488 + #gradient > .vertical-three-colors(#eee, #fff, 0.15, #fff);
489 + background-attachment: fixed;
490 + background-position: 0 40px;
491 + position: relative;
492 +}
493 +
494 // Give us some love
495 header,
496 section,
497 @@ -77,3 +84,30 @@ aside {
498 section {
499 margin-bottom: 40px;
500 }
501 +
502 +// Hashgrid grid
503 +/**
504 + * Grid
505 + */
506 +#grid {
507 + width: 980px;
508 + position: absolute;
509 + top: 0;
510 + left: 50%;
511 + margin-left: -490px;
512 +}
513 +#grid div.vert {
514 + width: 39px;
515 + border: solid darkturquoise;
516 + border-width: 0 1px;
517 + margin-right: 19px;
518 +}
519 +#grid div.vert.first-line {
520 + margin-left: 19px;
521 +}
522 +#grid div.horiz {
523 + height: 19px;
524 + border-bottom: 1px dotted rgba(255,0,0,.25);
525 + margin: 0;
526 + padding: 0;
527 +}
528 diff --git a/less/scaffolding.less b/less/scaffolding.less
529 index 2191d98b3f3f805b62eb3b60f1cb4f0be5bf1aa8..88bcf714a9f075cd7ba60033022f21ff95efc776 100644
530 --- a/less/scaffolding.less
531 +++ b/less/scaffolding.less
532 @@ -7,7 +7,7 @@
533 -------------------------------------------------- */
534
535 div.row {
536 - .clearfix;
537 + .clearfix();
538 div.span1 { .columns(1); }
539 div.span2 { .columns(2); }
540 div.span3 { .columns(3); }
541 @@ -34,8 +34,6 @@ html, body {
542 background-color: #fff;
543 }
544 body {
545 - #gradient > .vertical-three-colors(#eee, #fff, 0.25, #fff);
546 - background-attachment: fixed;
547 margin: 0;
548 .sans-serif(normal,14px,20px);
549 color: @gray;
550 diff --git a/readme.markdown b/readme.markdown
551 index 36b357b31afdee5fffbeddeba3144ffbd98d9778..41966346963824f2ce5497b8275c3a15c2044ac9 100644
552 --- a/readme.markdown
553 +++ b/readme.markdown
554 @@ -1,13 +1,4 @@
555 TODOS
556
557 -* Write "Using Twitter BP" section
558 -** Two ways to use: LESS.js or compiled
559 -** Not meant to be 100% bulletproof, but is 90% bulletproof (stats?)
560 -** Advanced framework for fast prototyping, internal app development, bootstraping new websites
561 -** Can be easily modified to provide more legacy support
562 -
563 -* Add grid examples back in
564 * Cross browser checks? Show this anywhere?
565 -* Add layouts section back in
566 -
567 -* Point JS libraries to public library links instead of within the repo
568 \ No newline at end of file
569 +* Add layouts section back in
570 \ No newline at end of file
@@ -0,0 +1,3 b''
1 diff --git a/work-horus.xls b/work-horus.xls
2 old mode 100644
3 new mode 100755
This diff has been collapsed as it changes many lines, (1094 lines changed) Show them Hide them
@@ -0,0 +1,1094 b''
1 diff --git a/US Warszawa.jpg b/US Warszawa.jpg
2 index 5a4b530a9322aad2b13d39a423a6a93146a09a29..b3b889566812a77f52376379e96d32d3db117150
3 GIT binary patch
4 literal 85172
5 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
1093 T!viJ;E(+{1YEXtu4BY<*Qm7~*
1094
@@ -0,0 +1,4 b''
1 diff --git a/work-horus.xls b/file.xls
2 similarity index 100%
3 rename from work-horus.xls
4 rename to file.xls
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: new file 100644
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,457 +1,381 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.changeset
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 changeset controller for pylons showoing changes beetween
7 7 revisions
8 8
9 9 :created_on: Apr 25, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 import logging
27 27 import traceback
28 28 from collections import defaultdict
29 29 from webob.exc import HTTPForbidden, HTTPBadRequest
30 30
31 31 from pylons import tmpl_context as c, url, request, response
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34 from pylons.decorators import jsonify
35 35
36 36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
37 37 ChangesetDoesNotExistError
38 38 from rhodecode.lib.vcs.nodes import FileNode
39 39
40 40 import rhodecode.lib.helpers as h
41 41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 42 from rhodecode.lib.base import BaseRepoController, render
43 43 from rhodecode.lib.utils import action_logger
44 44 from rhodecode.lib.compat import OrderedDict
45 45 from rhodecode.lib import diffs
46 46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
47 47 from rhodecode.model.comment import ChangesetCommentsModel
48 48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 49 from rhodecode.model.meta import Session
50 from rhodecode.lib.diffs import wrapped_diff
50 from rhodecode.lib.diffs import LimitedDiffContainer
51 51 from rhodecode.model.repo import RepoModel
52 52 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
53 53 from rhodecode.lib.vcs.backends.base import EmptyChangeset
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 def _update_with_GET(params, GET):
59 59 for k in ['diff1', 'diff2', 'diff']:
60 60 params[k] += GET.getall(k)
61 61
62 62
63 63 def anchor_url(revision, path, GET):
64 64 fid = h.FID(revision, path)
65 65 return h.url.current(anchor=fid, **dict(GET))
66 66
67 67
68 68 def get_ignore_ws(fid, GET):
69 69 ig_ws_global = GET.get('ignorews')
70 70 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
71 71 if ig_ws:
72 72 try:
73 73 return int(ig_ws[0].split(':')[-1])
74 74 except:
75 75 pass
76 76 return ig_ws_global
77 77
78 78
79 79 def _ignorews_url(GET, fileid=None):
80 80 fileid = str(fileid) if fileid else None
81 81 params = defaultdict(list)
82 82 _update_with_GET(params, GET)
83 83 lbl = _('show white space')
84 84 ig_ws = get_ignore_ws(fileid, GET)
85 85 ln_ctx = get_line_ctx(fileid, GET)
86 86 # global option
87 87 if fileid is None:
88 88 if ig_ws is None:
89 89 params['ignorews'] += [1]
90 90 lbl = _('ignore white space')
91 91 ctx_key = 'context'
92 92 ctx_val = ln_ctx
93 93 # per file options
94 94 else:
95 95 if ig_ws is None:
96 96 params[fileid] += ['WS:1']
97 97 lbl = _('ignore white space')
98 98
99 99 ctx_key = fileid
100 100 ctx_val = 'C:%s' % ln_ctx
101 101 # if we have passed in ln_ctx pass it along to our params
102 102 if ln_ctx:
103 103 params[ctx_key] += [ctx_val]
104 104
105 105 params['anchor'] = fileid
106 106 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
107 107 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
108 108
109 109
110 110 def get_line_ctx(fid, GET):
111 111 ln_ctx_global = GET.get('context')
112 if fid:
112 113 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
114 else:
115 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
116 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
117 if ln_ctx:
118 ln_ctx = [ln_ctx]
113 119
114 120 if ln_ctx:
115 121 retval = ln_ctx[0].split(':')[-1]
116 122 else:
117 123 retval = ln_ctx_global
118 124
119 125 try:
120 126 return int(retval)
121 127 except:
122 return
128 return 3
123 129
124 130
125 131 def _context_url(GET, fileid=None):
126 132 """
127 133 Generates url for context lines
128 134
129 135 :param fileid:
130 136 """
131 137
132 138 fileid = str(fileid) if fileid else None
133 139 ig_ws = get_ignore_ws(fileid, GET)
134 140 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
135 141
136 142 params = defaultdict(list)
137 143 _update_with_GET(params, GET)
138 144
139 145 # global option
140 146 if fileid is None:
141 147 if ln_ctx > 0:
142 148 params['context'] += [ln_ctx]
143 149
144 150 if ig_ws:
145 151 ig_ws_key = 'ignorews'
146 152 ig_ws_val = 1
147 153
148 154 # per file option
149 155 else:
150 156 params[fileid] += ['C:%s' % ln_ctx]
151 157 ig_ws_key = fileid
152 158 ig_ws_val = 'WS:%s' % 1
153 159
154 160 if ig_ws:
155 161 params[ig_ws_key] += [ig_ws_val]
156 162
157 163 lbl = _('%s line context') % ln_ctx
158 164
159 165 params['anchor'] = fileid
160 166 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
161 167 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
162 168
163 169
164 170 class ChangesetController(BaseRepoController):
165 171
166 172 @LoginRequired()
167 173 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
168 174 'repository.admin')
169 175 def __before__(self):
170 176 super(ChangesetController, self).__before__()
171 177 c.affected_files_cut_off = 60
172 178 repo_model = RepoModel()
173 179 c.users_array = repo_model.get_users_js()
174 180 c.users_groups_array = repo_model.get_users_groups_js()
175 181
176 182 def index(self, revision):
177
183 method = request.GET.get('diff', 'show')
178 184 c.anchor_url = anchor_url
179 185 c.ignorews_url = _ignorews_url
180 186 c.context_url = _context_url
181 187 limit_off = request.GET.get('fulldiff')
182 188 #get ranges of revisions if preset
183 189 rev_range = revision.split('...')[:2]
184 190 enable_comments = True
185 191 try:
186 192 if len(rev_range) == 2:
187 193 enable_comments = False
188 194 rev_start = rev_range[0]
189 195 rev_end = rev_range[1]
190 196 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
191 197 end=rev_end)
192 198 else:
193 199 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
194 200
195 201 c.cs_ranges = list(rev_ranges)
196 202 if not c.cs_ranges:
197 203 raise RepositoryError('Changeset range returned empty result')
198 204
199 205 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
200 206 log.error(traceback.format_exc())
201 207 h.flash(str(e), category='warning')
202 208 return redirect(url('home'))
203 209
204 210 c.changes = OrderedDict()
205 211
206 212 c.lines_added = 0 # count of lines added
207 213 c.lines_deleted = 0 # count of lines removes
208 214
209 cumulative_diff = 0
210 c.cut_off = False # defines if cut off limit is reached
211 215 c.changeset_statuses = ChangesetStatus.STATUSES
212 216 c.comments = []
213 217 c.statuses = []
214 218 c.inline_comments = []
215 219 c.inline_cnt = 0
220
216 221 # Iterate over ranges (default changeset view is always one changeset)
217 222 for changeset in c.cs_ranges:
218
223 inlines = []
224 if method == 'show':
219 225 c.statuses.extend([ChangesetStatusModel()\
220 226 .get_status(c.rhodecode_db_repo.repo_id,
221 227 changeset.raw_id)])
222 228
223 229 c.comments.extend(ChangesetCommentsModel()\
224 230 .get_comments(c.rhodecode_db_repo.repo_id,
225 231 revision=changeset.raw_id))
226 232 inlines = ChangesetCommentsModel()\
227 233 .get_inline_comments(c.rhodecode_db_repo.repo_id,
228 234 revision=changeset.raw_id)
229 235 c.inline_comments.extend(inlines)
236
230 237 c.changes[changeset.raw_id] = []
231 try:
232 changeset_parent = changeset.parents[0]
233 except IndexError:
234 changeset_parent = None
238
239 cs2 = changeset.raw_id
240 cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
241 context_lcl = get_line_ctx('', request.GET)
242 ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
235 243
236 #==================================================================
237 # ADDED FILES
238 #==================================================================
239 for node in changeset.added:
240 fid = h.FID(revision, node.path)
241 line_context_lcl = get_line_ctx(fid, request.GET)
242 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
243 lim = self.cut_off_limit
244 if cumulative_diff > self.cut_off_limit:
245 lim = -1 if limit_off is None else None
246 size, cs1, cs2, diff, st = wrapped_diff(
247 filenode_old=None,
248 filenode_new=node,
249 cut_off_limit=lim,
250 ignore_whitespace=ign_whitespace_lcl,
251 line_context=line_context_lcl,
252 enable_comments=enable_comments
253 )
254 cumulative_diff += size
244 _diff = c.rhodecode_repo.get_diff(cs1, cs2,
245 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
246 diff_limit = self.cut_off_limit if not limit_off else None
247 diff_processor = diffs.DiffProcessor(_diff,
248 vcs=c.rhodecode_repo.alias,
249 format='gitdiff',
250 diff_limit=diff_limit)
251 cs_changes = OrderedDict()
252 if method == 'show':
253 _parsed = diff_processor.prepare()
254 c.limited_diff = False
255 if isinstance(_parsed, LimitedDiffContainer):
256 c.limited_diff = True
257 for f in _parsed:
258 st = f['stats']
259 if st[0] != 'b':
255 260 c.lines_added += st[0]
256 261 c.lines_deleted += st[1]
257 c.changes[changeset.raw_id].append(
258 ('added', node, diff, cs1, cs2, st)
259 )
260
261 #==================================================================
262 # CHANGED FILES
263 #==================================================================
264 for node in changeset.changed:
265 try:
266 filenode_old = changeset_parent.get_node(node.path)
267 except ChangesetError:
268 log.warning('Unable to fetch parent node for diff')
269 filenode_old = FileNode(node.path, '', EmptyChangeset())
270
271 fid = h.FID(revision, node.path)
272 line_context_lcl = get_line_ctx(fid, request.GET)
273 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
274 lim = self.cut_off_limit
275 if cumulative_diff > self.cut_off_limit:
276 lim = -1 if limit_off is None else None
277 size, cs1, cs2, diff, st = wrapped_diff(
278 filenode_old=filenode_old,
279 filenode_new=node,
280 cut_off_limit=lim,
281 ignore_whitespace=ign_whitespace_lcl,
282 line_context=line_context_lcl,
283 enable_comments=enable_comments
284 )
285 cumulative_diff += size
286 c.lines_added += st[0]
287 c.lines_deleted += st[1]
288 c.changes[changeset.raw_id].append(
289 ('changed', node, diff, cs1, cs2, st)
290 )
291 #==================================================================
292 # REMOVED FILES
293 #==================================================================
294 for node in changeset.removed:
295 c.changes[changeset.raw_id].append(
296 ('removed', node, None, None, None, (0, 0))
297 )
262 fid = h.FID(changeset.raw_id, f['filename'])
263 diff = diff_processor.as_html(enable_comments=enable_comments,
264 parsed_lines=[f])
265 cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
266 diff, st]
267 else:
268 # downloads/raw we only need RAW diff nothing else
269 diff = diff_processor.as_raw()
270 cs_changes[''] = [None, None, None, None, diff, None]
271 c.changes[changeset.raw_id] = cs_changes
298 272
299 273 # count inline comments
300 274 for __, lines in c.inline_comments:
301 275 for comments in lines.values():
302 276 c.inline_cnt += len(comments)
303 277
304 278 if len(c.cs_ranges) == 1:
305 279 c.changeset = c.cs_ranges[0]
306 c.changes = c.changes[c.changeset.raw_id]
307
280 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
281 for x in c.changeset.parents])
282 if method == 'download':
283 response.content_type = 'text/plain'
284 response.content_disposition = 'attachment; filename=%s.patch' \
285 % revision
286 return render('changeset/raw_changeset.html')
287 elif method == 'raw':
288 response.content_type = 'text/plain'
289 return render('changeset/raw_changeset.html')
290 elif method == 'show':
291 if len(c.cs_ranges) == 1:
308 292 return render('changeset/changeset.html')
309 293 else:
310 294 return render('changeset/changeset_range.html')
311 295
312 296 def raw_changeset(self, revision):
313
314 method = request.GET.get('diff', 'show')
315 ignore_whitespace = request.GET.get('ignorews') == '1'
316 line_context = request.GET.get('context', 3)
317 try:
318 c.scm_type = c.rhodecode_repo.alias
319 c.changeset = c.rhodecode_repo.get_changeset(revision)
320 except RepositoryError:
321 log.error(traceback.format_exc())
322 return redirect(url('home'))
323 else:
324 try:
325 c.changeset_parent = c.changeset.parents[0]
326 except IndexError:
327 c.changeset_parent = None
328 c.changes = []
329
330 for node in c.changeset.added:
331 filenode_old = FileNode(node.path, '')
332 if filenode_old.is_binary or node.is_binary:
333 diff = _('binary file') + '\n'
334 else:
335 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
336 ignore_whitespace=ignore_whitespace,
337 context=line_context)
338 diff = diffs.DiffProcessor(f_gitdiff,
339 format='gitdiff').raw_diff()
340
341 cs1 = None
342 cs2 = node.changeset.raw_id
343 c.changes.append(('added', node, diff, cs1, cs2))
344
345 for node in c.changeset.changed:
346 filenode_old = c.changeset_parent.get_node(node.path)
347 if filenode_old.is_binary or node.is_binary:
348 diff = _('binary file')
349 else:
350 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
351 ignore_whitespace=ignore_whitespace,
352 context=line_context)
353 diff = diffs.DiffProcessor(f_gitdiff,
354 format='gitdiff').raw_diff()
355
356 cs1 = filenode_old.changeset.raw_id
357 cs2 = node.changeset.raw_id
358 c.changes.append(('changed', node, diff, cs1, cs2))
359
360 response.content_type = 'text/plain'
361
362 if method == 'download':
363 response.content_disposition = 'attachment; filename=%s.patch' \
364 % revision
365
366 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
367 for x in c.changeset.parents])
368
369 c.diffs = ''
370 for x in c.changes:
371 c.diffs += x[2]
372
373 return render('changeset/raw_changeset.html')
297 return self.index(revision)
374 298
375 299 @jsonify
376 300 def comment(self, repo_name, revision):
377 301 status = request.POST.get('changeset_status')
378 302 change_status = request.POST.get('change_changeset_status')
379 303 text = request.POST.get('text')
380 304 if status and change_status:
381 305 text = text or (_('Status change -> %s')
382 306 % ChangesetStatus.get_status_lbl(status))
383 307
384 308 comm = ChangesetCommentsModel().create(
385 309 text=text,
386 310 repo=c.rhodecode_db_repo.repo_id,
387 311 user=c.rhodecode_user.user_id,
388 312 revision=revision,
389 313 f_path=request.POST.get('f_path'),
390 314 line_no=request.POST.get('line'),
391 315 status_change=(ChangesetStatus.get_status_lbl(status)
392 316 if status and change_status else None)
393 317 )
394 318
395 319 # get status if set !
396 320 if status and change_status:
397 321 # if latest status was from pull request and it's closed
398 322 # disallow changing status !
399 323 # dont_allow_on_closed_pull_request = True !
400 324
401 325 try:
402 326 ChangesetStatusModel().set_status(
403 327 c.rhodecode_db_repo.repo_id,
404 328 status,
405 329 c.rhodecode_user.user_id,
406 330 comm,
407 331 revision=revision,
408 332 dont_allow_on_closed_pull_request=True
409 333 )
410 334 except StatusChangeOnClosedPullRequestError:
411 335 log.error(traceback.format_exc())
412 336 msg = _('Changing status on a changeset associated with'
413 337 'a closed pull request is not allowed')
414 338 h.flash(msg, category='warning')
415 339 return redirect(h.url('changeset_home', repo_name=repo_name,
416 340 revision=revision))
417 341 action_logger(self.rhodecode_user,
418 342 'user_commented_revision:%s' % revision,
419 343 c.rhodecode_db_repo, self.ip_addr, self.sa)
420 344
421 345 Session().commit()
422 346
423 347 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
424 348 return redirect(h.url('changeset_home', repo_name=repo_name,
425 349 revision=revision))
426 350
427 351 data = {
428 352 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
429 353 }
430 354 if comm:
431 355 c.co = comm
432 356 data.update(comm.get_dict())
433 357 data.update({'rendered_text':
434 358 render('changeset/changeset_comment_block.html')})
435 359
436 360 return data
437 361
438 362 @jsonify
439 363 def delete_comment(self, repo_name, comment_id):
440 364 co = ChangesetComment.get(comment_id)
441 365 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
442 366 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
443 367 ChangesetCommentsModel().delete(comment=co)
444 368 Session().commit()
445 369 return True
446 370 else:
447 371 raise HTTPForbidden()
448 372
449 373 @jsonify
450 374 def changeset_info(self, repo_name, revision):
451 375 if request.is_xhr:
452 376 try:
453 377 return c.rhodecode_repo.get_changeset(revision)
454 378 except ChangesetDoesNotExistError, e:
455 379 return EmptyChangeset(message=str(e))
456 380 else:
457 381 raise HTTPBadRequest()
@@ -1,148 +1,148 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.compare
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 compare controller for pylons showoing differences between two
7 7 repos, branches, bookmarks or tips
8 8
9 9 :created_on: May 6, 2012
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software: you can redistribute it and/or modify
15 15 # it under the terms of the GNU General Public License as published by
16 16 # the Free Software Foundation, either version 3 of the License, or
17 17 # (at your option) any later version.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 26 import logging
27 27 import traceback
28 28
29 29 from webob.exc import HTTPNotFound
30 30 from pylons import request, response, session, tmpl_context as c, url
31 31 from pylons.controllers.util import abort, redirect
32 32 from pylons.i18n.translation import _
33 33
34 34 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
35 35 from rhodecode.lib import helpers as h
36 36 from rhodecode.lib.base import BaseRepoController, render
37 37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 38 from rhodecode.lib import diffs
39 39
40 40 from rhodecode.model.db import Repository
41 41 from rhodecode.model.pull_request import PullRequestModel
42 42 from webob.exc import HTTPBadRequest
43 43 from rhodecode.lib.utils2 import str2bool
44 44
45 45 log = logging.getLogger(__name__)
46 46
47 47
48 48 class CompareController(BaseRepoController):
49 49
50 50 @LoginRequired()
51 51 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
52 52 'repository.admin')
53 53 def __before__(self):
54 54 super(CompareController, self).__before__()
55 55
56 56 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
57 57 partial=False):
58 58 """
59 59 Safe way to get changeset if error occur it redirects to changeset with
60 60 proper message. If partial is set then don't do redirect raise Exception
61 61 instead
62 62
63 63 :param rev: revision to fetch
64 64 :param repo: repo instance
65 65 """
66 66
67 67 try:
68 68 type_, rev = rev
69 69 return repo.scm_instance.get_changeset(rev)
70 70 except EmptyRepositoryError, e:
71 71 if not redirect_after:
72 72 return None
73 73 h.flash(h.literal(_('There are no changesets yet')),
74 74 category='warning')
75 75 redirect(url('summary_home', repo_name=repo.repo_name))
76 76
77 77 except RepositoryError, e:
78 78 log.error(traceback.format_exc())
79 79 h.flash(str(e), category='warning')
80 80 if not partial:
81 81 redirect(h.url('summary_home', repo_name=repo.repo_name))
82 82 raise HTTPBadRequest()
83 83
84 84 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
85 85
86 86 org_repo = c.rhodecode_db_repo.repo_name
87 87 org_ref = (org_ref_type, org_ref)
88 88 other_ref = (other_ref_type, other_ref)
89 89 other_repo = request.GET.get('repo', org_repo)
90 90 bundle_compare = str2bool(request.GET.get('bundle', True))
91 91
92 92 c.swap_url = h.url('compare_url', repo_name=other_repo,
93 93 org_ref_type=other_ref[0], org_ref=other_ref[1],
94 94 other_ref_type=org_ref[0], other_ref=org_ref[1],
95 95 repo=org_repo, as_form=request.GET.get('as_form'),
96 96 bundle=bundle_compare)
97 97
98 98 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
99 99 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
100 100
101 101 if c.org_repo is None or c.other_repo is None:
102 102 log.error('Could not found repo %s or %s' % (org_repo, other_repo))
103 103 raise HTTPNotFound
104 104
105 105 if c.org_repo.scm_instance.alias != 'hg':
106 106 log.error('Review not available for GIT REPOS')
107 107 raise HTTPNotFound
108 108 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
109 109 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
110 110 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
111 111
112 112 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
113 113 org_repo, org_ref, other_repo, other_ref
114 114 )
115 115
116 116 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
117 117 c.cs_ranges])
118 118 c.target_repo = c.repo_name
119 119 # defines that we need hidden inputs with changesets
120 120 c.as_form = request.GET.get('as_form', False)
121 121 if partial:
122 122 return render('compare/compare_cs.html')
123 123
124 124 if not bundle_compare and c.cs_ranges:
125 125 # case we want a simple diff without incoming changesets, just
126 126 # for review purposes. Make the diff on the forked repo, with
127 127 # revision that is common ancestor
128 128 other_ref = ('rev', c.cs_ranges[-1].parents[0].raw_id)
129 129 other_repo = org_repo
130 130
131 131 c.org_ref = org_ref[1]
132 132 c.other_ref = other_ref[1]
133 133
134 134 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
135 135 discovery_data, bundle_compare=bundle_compare)
136 136 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
137 137 _parsed = diff_processor.prepare()
138 138
139 139 c.files = []
140 140 c.changes = {}
141 141
142 142 for f in _parsed:
143 143 fid = h.FID('', f['filename'])
144 144 c.files.append([fid, f['operation'], f['filename'], f['stats']])
145 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
145 diff = diff_processor.as_html(enable_comments=False, parsed_lines=[f])
146 146 c.changes[fid] = [f['operation'], f['filename'], diff]
147 147
148 148 return render('compare/compare_diff.html')
@@ -1,556 +1,574 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.files
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Files controller for RhodeCode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 from __future__ import with_statement
26 26 import os
27 27 import logging
28 28 import traceback
29 29 import tempfile
30 30
31 31 from pylons import request, response, tmpl_context as c, url
32 32 from pylons.i18n.translation import _
33 33 from pylons.controllers.util import redirect
34 34 from pylons.decorators import jsonify
35 35
36 36 from rhodecode.lib import diffs
37 37 from rhodecode.lib import helpers as h
38 38
39 39 from rhodecode.lib.compat import OrderedDict
40 40 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str,\
41 41 str2bool
42 42 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
43 43 from rhodecode.lib.base import BaseRepoController, render
44 44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 45 from rhodecode.lib.vcs.conf import settings
46 46 from rhodecode.lib.vcs.exceptions import RepositoryError, \
47 47 ChangesetDoesNotExistError, EmptyRepositoryError, \
48 48 ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError,\
49 49 NodeDoesNotExistError, ChangesetError
50 50 from rhodecode.lib.vcs.nodes import FileNode
51 51
52 52 from rhodecode.model.repo import RepoModel
53 53 from rhodecode.model.scm import ScmModel
54 54 from rhodecode.model.db import Repository
55 55
56 56 from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
57 57 _context_url, get_line_ctx, get_ignore_ws
58 58
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class FilesController(BaseRepoController):
64 64
65 65 def __before__(self):
66 66 super(FilesController, self).__before__()
67 67 c.cut_off_limit = self.cut_off_limit
68 68
69 69 def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
70 70 """
71 71 Safe way to get changeset if error occur it redirects to tip with
72 72 proper message
73 73
74 74 :param rev: revision to fetch
75 75 :param repo_name: repo name to redirect after
76 76 """
77 77
78 78 try:
79 79 return c.rhodecode_repo.get_changeset(rev)
80 80 except EmptyRepositoryError, e:
81 81 if not redirect_after:
82 82 return None
83 83 url_ = url('files_add_home',
84 84 repo_name=c.repo_name,
85 85 revision=0, f_path='')
86 86 add_new = '<a href="%s">[%s]</a>' % (url_, _('click here to add new file'))
87 87 h.flash(h.literal(_('There are no files yet %s') % add_new),
88 88 category='warning')
89 89 redirect(h.url('summary_home', repo_name=repo_name))
90 90
91 91 except RepositoryError, e:
92 92 h.flash(str(e), category='warning')
93 93 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
94 94
95 95 def __get_filenode_or_redirect(self, repo_name, cs, path):
96 96 """
97 97 Returns file_node, if error occurs or given path is directory,
98 98 it'll redirect to top level path
99 99
100 100 :param repo_name: repo_name
101 101 :param cs: given changeset
102 102 :param path: path to lookup
103 103 """
104 104
105 105 try:
106 106 file_node = cs.get_node(path)
107 107 if file_node.is_dir():
108 108 raise RepositoryError('given path is a directory')
109 109 except RepositoryError, e:
110 110 h.flash(str(e), category='warning')
111 111 redirect(h.url('files_home', repo_name=repo_name,
112 112 revision=cs.raw_id))
113 113
114 114 return file_node
115 115
116 116 @LoginRequired()
117 117 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
118 118 'repository.admin')
119 119 def index(self, repo_name, revision, f_path, annotate=False):
120 120 # redirect to given revision from form if given
121 121 post_revision = request.POST.get('at_rev', None)
122 122 if post_revision:
123 123 cs = self.__get_cs_or_redirect(post_revision, repo_name)
124 124 redirect(url('files_home', repo_name=c.repo_name,
125 125 revision=cs.raw_id, f_path=f_path))
126 126
127 127 c.changeset = self.__get_cs_or_redirect(revision, repo_name)
128 128 c.branch = request.GET.get('branch', None)
129 129 c.f_path = f_path
130 130 c.annotate = annotate
131 131 cur_rev = c.changeset.revision
132 132
133 133 # prev link
134 134 try:
135 135 prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
136 136 c.url_prev = url('files_home', repo_name=c.repo_name,
137 137 revision=prev_rev.raw_id, f_path=f_path)
138 138 if c.branch:
139 139 c.url_prev += '?branch=%s' % c.branch
140 140 except (ChangesetDoesNotExistError, VCSError):
141 141 c.url_prev = '#'
142 142
143 143 # next link
144 144 try:
145 145 next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
146 146 c.url_next = url('files_home', repo_name=c.repo_name,
147 147 revision=next_rev.raw_id, f_path=f_path)
148 148 if c.branch:
149 149 c.url_next += '?branch=%s' % c.branch
150 150 except (ChangesetDoesNotExistError, VCSError):
151 151 c.url_next = '#'
152 152
153 153 # files or dirs
154 154 try:
155 155 c.file = c.changeset.get_node(f_path)
156 156
157 157 if c.file.is_file():
158 158 c.file_history, _hist = self._get_node_history(c.changeset, f_path)
159 159 c.file_changeset = c.changeset
160 160 if _hist:
161 161 c.file_changeset = (c.changeset
162 162 if c.changeset.revision < _hist[0].revision
163 163 else _hist[0])
164 164 c.authors = []
165 165 for a in set([x.author for x in _hist]):
166 166 c.authors.append((h.email(a), h.person(a)))
167 167 else:
168 168 c.authors = c.file_history = []
169 169 except RepositoryError, e:
170 170 h.flash(str(e), category='warning')
171 171 redirect(h.url('files_home', repo_name=repo_name,
172 172 revision='tip'))
173 173
174 174 if request.environ.get('HTTP_X_PARTIAL_XHR'):
175 175 return render('files/files_ypjax.html')
176 176
177 177 return render('files/files.html')
178 178
179 179 @LoginRequired()
180 180 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
181 181 'repository.admin')
182 182 def rawfile(self, repo_name, revision, f_path):
183 183 cs = self.__get_cs_or_redirect(revision, repo_name)
184 184 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
185 185
186 186 response.content_disposition = 'attachment; filename=%s' % \
187 187 safe_str(f_path.split(Repository.url_sep())[-1])
188 188
189 189 response.content_type = file_node.mimetype
190 190 return file_node.content
191 191
192 192 @LoginRequired()
193 193 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
194 194 'repository.admin')
195 195 def raw(self, repo_name, revision, f_path):
196 196 cs = self.__get_cs_or_redirect(revision, repo_name)
197 197 file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
198 198
199 199 raw_mimetype_mapping = {
200 200 # map original mimetype to a mimetype used for "show as raw"
201 201 # you can also provide a content-disposition to override the
202 202 # default "attachment" disposition.
203 203 # orig_type: (new_type, new_dispo)
204 204
205 205 # show images inline:
206 206 'image/x-icon': ('image/x-icon', 'inline'),
207 207 'image/png': ('image/png', 'inline'),
208 208 'image/gif': ('image/gif', 'inline'),
209 209 'image/jpeg': ('image/jpeg', 'inline'),
210 210 'image/svg+xml': ('image/svg+xml', 'inline'),
211 211 }
212 212
213 213 mimetype = file_node.mimetype
214 214 try:
215 215 mimetype, dispo = raw_mimetype_mapping[mimetype]
216 216 except KeyError:
217 217 # we don't know anything special about this, handle it safely
218 218 if file_node.is_binary:
219 219 # do same as download raw for binary files
220 220 mimetype, dispo = 'application/octet-stream', 'attachment'
221 221 else:
222 222 # do not just use the original mimetype, but force text/plain,
223 223 # otherwise it would serve text/html and that might be unsafe.
224 224 # Note: underlying vcs library fakes text/plain mimetype if the
225 225 # mimetype can not be determined and it thinks it is not
226 226 # binary.This might lead to erroneous text display in some
227 227 # cases, but helps in other cases, like with text files
228 228 # without extension.
229 229 mimetype, dispo = 'text/plain', 'inline'
230 230
231 231 if dispo == 'attachment':
232 232 dispo = 'attachment; filename=%s' % \
233 233 safe_str(f_path.split(os.sep)[-1])
234 234
235 235 response.content_disposition = dispo
236 236 response.content_type = mimetype
237 237 return file_node.content
238 238
239 239 @LoginRequired()
240 240 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
241 241 def edit(self, repo_name, revision, f_path):
242 242 repo = Repository.get_by_repo_name(repo_name)
243 243 if repo.enable_locking and repo.locked[0]:
244 244 h.flash(_('This repository is has been locked by %s on %s')
245 245 % (h.person_by_id(repo.locked[0]),
246 246 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
247 247 'warning')
248 248 return redirect(h.url('files_home',
249 249 repo_name=repo_name, revision='tip'))
250 250
251 251 r_post = request.POST
252 252
253 253 c.cs = self.__get_cs_or_redirect(revision, repo_name)
254 254 c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
255 255
256 256 if c.file.is_binary:
257 257 return redirect(url('files_home', repo_name=c.repo_name,
258 258 revision=c.cs.raw_id, f_path=f_path))
259 259
260 260 c.f_path = f_path
261 261
262 262 if r_post:
263 263
264 264 old_content = c.file.content
265 265 sl = old_content.splitlines(1)
266 266 first_line = sl[0] if sl else ''
267 267 # modes: 0 - Unix, 1 - Mac, 2 - DOS
268 268 mode = detect_mode(first_line, 0)
269 269 content = convert_line_endings(r_post.get('content'), mode)
270 270
271 271 message = r_post.get('message') or (_('Edited %s via RhodeCode')
272 272 % (f_path))
273 273 author = self.rhodecode_user.full_contact
274 274
275 275 if content == old_content:
276 276 h.flash(_('No changes'),
277 277 category='warning')
278 278 return redirect(url('changeset_home', repo_name=c.repo_name,
279 279 revision='tip'))
280 280
281 281 try:
282 282 self.scm_model.commit_change(repo=c.rhodecode_repo,
283 283 repo_name=repo_name, cs=c.cs,
284 284 user=self.rhodecode_user,
285 285 author=author, message=message,
286 286 content=content, f_path=f_path)
287 287 h.flash(_('Successfully committed to %s') % f_path,
288 288 category='success')
289 289
290 290 except Exception:
291 291 log.error(traceback.format_exc())
292 292 h.flash(_('Error occurred during commit'), category='error')
293 293 return redirect(url('changeset_home',
294 294 repo_name=c.repo_name, revision='tip'))
295 295
296 296 return render('files/files_edit.html')
297 297
298 298 @LoginRequired()
299 299 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
300 300 def add(self, repo_name, revision, f_path):
301 301
302 302 repo = Repository.get_by_repo_name(repo_name)
303 303 if repo.enable_locking and repo.locked[0]:
304 304 h.flash(_('This repository is has been locked by %s on %s')
305 305 % (h.person_by_id(repo.locked[0]),
306 306 h.fmt_date(h.time_to_datetime(repo.locked[1]))),
307 307 'warning')
308 308 return redirect(h.url('files_home',
309 309 repo_name=repo_name, revision='tip'))
310 310
311 311 r_post = request.POST
312 312 c.cs = self.__get_cs_or_redirect(revision, repo_name,
313 313 redirect_after=False)
314 314 if c.cs is None:
315 315 c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
316 316
317 317 c.f_path = f_path
318 318
319 319 if r_post:
320 320 unix_mode = 0
321 321 content = convert_line_endings(r_post.get('content'), unix_mode)
322 322
323 323 message = r_post.get('message') or (_('Added %s via RhodeCode')
324 324 % (f_path))
325 325 location = r_post.get('location')
326 326 filename = r_post.get('filename')
327 327 file_obj = r_post.get('upload_file', None)
328 328
329 329 if file_obj is not None and hasattr(file_obj, 'filename'):
330 330 filename = file_obj.filename
331 331 content = file_obj.file
332 332
333 333 node_path = os.path.join(location, filename)
334 334 author = self.rhodecode_user.full_contact
335 335
336 336 if not content:
337 337 h.flash(_('No content'), category='warning')
338 338 return redirect(url('changeset_home', repo_name=c.repo_name,
339 339 revision='tip'))
340 340 if not filename:
341 341 h.flash(_('No filename'), category='warning')
342 342 return redirect(url('changeset_home', repo_name=c.repo_name,
343 343 revision='tip'))
344 344
345 345 try:
346 346 self.scm_model.create_node(repo=c.rhodecode_repo,
347 347 repo_name=repo_name, cs=c.cs,
348 348 user=self.rhodecode_user,
349 349 author=author, message=message,
350 350 content=content, f_path=node_path)
351 351 h.flash(_('Successfully committed to %s') % node_path,
352 352 category='success')
353 353 except NodeAlreadyExistsError, e:
354 354 h.flash(_(e), category='error')
355 355 except Exception:
356 356 log.error(traceback.format_exc())
357 357 h.flash(_('Error occurred during commit'), category='error')
358 358 return redirect(url('changeset_home',
359 359 repo_name=c.repo_name, revision='tip'))
360 360
361 361 return render('files/files_add.html')
362 362
363 363 @LoginRequired()
364 364 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
365 365 'repository.admin')
366 366 def archivefile(self, repo_name, fname):
367 367
368 368 fileformat = None
369 369 revision = None
370 370 ext = None
371 371 subrepos = request.GET.get('subrepos') == 'true'
372 372
373 373 for a_type, ext_data in settings.ARCHIVE_SPECS.items():
374 374 archive_spec = fname.split(ext_data[1])
375 375 if len(archive_spec) == 2 and archive_spec[1] == '':
376 376 fileformat = a_type or ext_data[1]
377 377 revision = archive_spec[0]
378 378 ext = ext_data[1]
379 379
380 380 try:
381 381 dbrepo = RepoModel().get_by_repo_name(repo_name)
382 382 if dbrepo.enable_downloads is False:
383 383 return _('downloads disabled')
384 384
385 385 if c.rhodecode_repo.alias == 'hg':
386 386 # patch and reset hooks section of UI config to not run any
387 387 # hooks on fetching archives with subrepos
388 388 for k, v in c.rhodecode_repo._repo.ui.configitems('hooks'):
389 389 c.rhodecode_repo._repo.ui.setconfig('hooks', k, None)
390 390
391 391 cs = c.rhodecode_repo.get_changeset(revision)
392 392 content_type = settings.ARCHIVE_SPECS[fileformat][0]
393 393 except ChangesetDoesNotExistError:
394 394 return _('Unknown revision %s') % revision
395 395 except EmptyRepositoryError:
396 396 return _('Empty repository')
397 397 except (ImproperArchiveTypeError, KeyError):
398 398 return _('Unknown archive type')
399 399
400 400 fd, archive = tempfile.mkstemp()
401 401 t = open(archive, 'wb')
402 402 cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
403 403 t.close()
404 404
405 405 def get_chunked_archive(archive):
406 406 stream = open(archive, 'rb')
407 407 while True:
408 408 data = stream.read(16 * 1024)
409 409 if not data:
410 410 stream.close()
411 411 os.close(fd)
412 412 os.remove(archive)
413 413 break
414 414 yield data
415 415
416 416 response.content_disposition = str('attachment; filename=%s-%s%s' \
417 417 % (repo_name, revision[:12], ext))
418 418 response.content_type = str(content_type)
419 419 return get_chunked_archive(archive)
420 420
421 421 @LoginRequired()
422 422 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
423 423 'repository.admin')
424 424 def diff(self, repo_name, f_path):
425 425 ignore_whitespace = request.GET.get('ignorews') == '1'
426 426 line_context = request.GET.get('context', 3)
427 427 diff1 = request.GET.get('diff1', '')
428 428 diff2 = request.GET.get('diff2', '')
429 429 c.action = request.GET.get('diff')
430 430 c.no_changes = diff1 == diff2
431 431 c.f_path = f_path
432 432 c.big_diff = False
433 433 c.anchor_url = anchor_url
434 434 c.ignorews_url = _ignorews_url
435 435 c.context_url = _context_url
436 436 c.changes = OrderedDict()
437 437 c.changes[diff2] = []
438 438
439 439 #special case if we want a show rev only, it's impl here
440 440 #to reduce JS and callbacks
441
441 442 if request.GET.get('show_rev'):
442 443 if str2bool(request.GET.get('annotate', 'False')):
443 444 _url = url('files_annotate_home', repo_name=c.repo_name,
444 445 revision=diff1, f_path=c.f_path)
445 446 else:
446 447 _url = url('files_home', repo_name=c.repo_name,
447 448 revision=diff1, f_path=c.f_path)
448 449
449 450 return redirect(_url)
450 451 try:
451 452 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
452 453 c.changeset_1 = c.rhodecode_repo.get_changeset(diff1)
454 try:
453 455 node1 = c.changeset_1.get_node(f_path)
456 except NodeDoesNotExistError:
457 c.changeset_1 = EmptyChangeset(cs=diff1,
458 revision=c.changeset_1.revision,
459 repo=c.rhodecode_repo)
460 node1 = FileNode(f_path, '', changeset=c.changeset_1)
454 461 else:
455 462 c.changeset_1 = EmptyChangeset(repo=c.rhodecode_repo)
456 node1 = FileNode('.', '', changeset=c.changeset_1)
463 node1 = FileNode(f_path, '', changeset=c.changeset_1)
457 464
458 465 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
459 466 c.changeset_2 = c.rhodecode_repo.get_changeset(diff2)
467 try:
460 468 node2 = c.changeset_2.get_node(f_path)
469 except NodeDoesNotExistError:
470 c.changeset_2 = EmptyChangeset(cs=diff2,
471 revision=c.changeset_2.revision,
472 repo=c.rhodecode_repo)
473 node2 = FileNode(f_path, '', changeset=c.changeset_2)
461 474 else:
462 475 c.changeset_2 = EmptyChangeset(repo=c.rhodecode_repo)
463 node2 = FileNode('.', '', changeset=c.changeset_2)
476 node2 = FileNode(f_path, '', changeset=c.changeset_2)
464 477 except RepositoryError:
478 log.error(traceback.format_exc())
465 479 return redirect(url('files_home', repo_name=c.repo_name,
466 480 f_path=f_path))
467 481
468 482 if c.action == 'download':
469 483 _diff = diffs.get_gitdiff(node1, node2,
470 484 ignore_whitespace=ignore_whitespace,
471 485 context=line_context)
472 486 diff = diffs.DiffProcessor(_diff, format='gitdiff')
473 487
474 488 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
475 489 response.content_type = 'text/plain'
476 490 response.content_disposition = (
477 491 'attachment; filename=%s' % diff_name
478 492 )
479 return diff.raw_diff()
493 return diff.as_raw()
480 494
481 495 elif c.action == 'raw':
482 496 _diff = diffs.get_gitdiff(node1, node2,
483 497 ignore_whitespace=ignore_whitespace,
484 498 context=line_context)
485 499 diff = diffs.DiffProcessor(_diff, format='gitdiff')
486 500 response.content_type = 'text/plain'
487 return diff.raw_diff()
501 return diff.as_raw()
488 502
489 503 else:
490 504 fid = h.FID(diff2, node2.path)
491 505 line_context_lcl = get_line_ctx(fid, request.GET)
492 506 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
493 507
494 508 lim = request.GET.get('fulldiff') or self.cut_off_limit
495 509 _, cs1, cs2, diff, st = diffs.wrapped_diff(filenode_old=node1,
496 510 filenode_new=node2,
497 511 cut_off_limit=lim,
498 512 ignore_whitespace=ign_whitespace_lcl,
499 513 line_context=line_context_lcl,
500 514 enable_comments=False)
501
502 c.changes = [('', node2, diff, cs1, cs2, st,)]
515 op = ''
516 filename = node1.path
517 cs_changes = {
518 'fid': [cs1, cs2, op, filename, diff, st]
519 }
520 c.changes = cs_changes
503 521
504 522 return render('files/file_diff.html')
505 523
506 524 def _get_node_history(self, cs, f_path, changesets=None):
507 525 """
508 526 get changesets history for given node
509 527
510 528 :param cs: changeset to calculate history
511 529 :param f_path: path for node to calculate history for
512 530 :param changesets: if passed don't calculate history and take
513 531 changesets defined in this list
514 532 """
515 533 # calculate history based on tip
516 534 tip_cs = c.rhodecode_repo.get_changeset()
517 535 if changesets is None:
518 536 try:
519 537 changesets = tip_cs.get_file_history(f_path)
520 538 except (NodeDoesNotExistError, ChangesetError):
521 539 #this node is not present at tip !
522 540 changesets = cs.get_file_history(f_path)
523 541
524 542 hist_l = []
525 543
526 544 changesets_group = ([], _("Changesets"))
527 545 branches_group = ([], _("Branches"))
528 546 tags_group = ([], _("Tags"))
529 547 _hg = cs.repository.alias == 'hg'
530 548 for chs in changesets:
531 549 _branch = '(%s)' % chs.branch if _hg else ''
532 550 n_desc = 'r%s:%s %s' % (chs.revision, chs.short_id, _branch)
533 551 changesets_group[0].append((chs.raw_id, n_desc,))
534 552
535 553 hist_l.append(changesets_group)
536 554
537 555 for name, chs in c.rhodecode_repo.branches.items():
538 556 branches_group[0].append((chs, name),)
539 557 hist_l.append(branches_group)
540 558
541 559 for name, chs in c.rhodecode_repo.tags.items():
542 560 tags_group[0].append((chs, name),)
543 561 hist_l.append(tags_group)
544 562
545 563 return hist_l, changesets
546 564
547 565 @LoginRequired()
548 566 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
549 567 'repository.admin')
550 568 @jsonify
551 569 def nodelist(self, repo_name, revision, f_path):
552 570 if request.environ.get('HTTP_X_PARTIAL_XHR'):
553 571 cs = self.__get_cs_or_redirect(revision, repo_name)
554 572 _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
555 573 flat=False)
556 574 return {'nodes': _d + _f}
@@ -1,446 +1,446 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.pullrequests
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 pull requests controller for rhodecode for initializing pull requests
7 7
8 8 :created_on: May 7, 2012
9 9 :author: marcink
10 10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software: you can redistribute it and/or modify
14 14 # it under the terms of the GNU General Public License as published by
15 15 # the Free Software Foundation, either version 3 of the License, or
16 16 # (at your option) any later version.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 25 import logging
26 26 import traceback
27 27 import formencode
28 28
29 29 from webob.exc import HTTPNotFound, HTTPForbidden
30 30 from collections import defaultdict
31 31 from itertools import groupby
32 32
33 33 from pylons import request, response, session, tmpl_context as c, url
34 34 from pylons.controllers.util import abort, redirect
35 35 from pylons.i18n.translation import _
36 36 from pylons.decorators import jsonify
37 37
38 38 from rhodecode.lib.compat import json
39 39 from rhodecode.lib.base import BaseRepoController, render
40 40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
41 41 NotAnonymous
42 42 from rhodecode.lib import helpers as h
43 43 from rhodecode.lib import diffs
44 44 from rhodecode.lib.utils import action_logger
45 45 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
46 46 ChangesetComment
47 47 from rhodecode.model.pull_request import PullRequestModel
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.model.repo import RepoModel
50 50 from rhodecode.model.comment import ChangesetCommentsModel
51 51 from rhodecode.model.changeset_status import ChangesetStatusModel
52 52 from rhodecode.model.forms import PullRequestForm
53 53 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
54 54
55 55 log = logging.getLogger(__name__)
56 56
57 57
58 58 class PullrequestsController(BaseRepoController):
59 59
60 60 @LoginRequired()
61 61 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
62 62 'repository.admin')
63 63 def __before__(self):
64 64 super(PullrequestsController, self).__before__()
65 65 repo_model = RepoModel()
66 66 c.users_array = repo_model.get_users_js()
67 67 c.users_groups_array = repo_model.get_users_groups_js()
68 68
69 69 def _get_repo_refs(self, repo):
70 70 hist_l = []
71 71
72 72 branches_group = ([('branch:%s:%s' % (k, v), k) for
73 73 k, v in repo.branches.iteritems()], _("Branches"))
74 74 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
75 75 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
76 76 tags_group = ([('tag:%s:%s' % (k, v), k) for
77 77 k, v in repo.tags.iteritems()], _("Tags"))
78 78
79 79 hist_l.append(bookmarks_group)
80 80 hist_l.append(branches_group)
81 81 hist_l.append(tags_group)
82 82
83 83 return hist_l
84 84
85 85 def _get_default_rev(self, repo):
86 86 """
87 87 Get's default revision to do compare on pull request
88 88
89 89 :param repo:
90 90 """
91 91 repo = repo.scm_instance
92 92 if 'default' in repo.branches:
93 93 return 'default'
94 94 else:
95 95 #if repo doesn't have default branch return first found
96 96 return repo.branches.keys()[0]
97 97
98 98 def show_all(self, repo_name):
99 99 c.pull_requests = PullRequestModel().get_all(repo_name)
100 100 c.repo_name = repo_name
101 101 return render('/pullrequests/pullrequest_show_all.html')
102 102
103 103 @NotAnonymous()
104 104 def index(self):
105 105 org_repo = c.rhodecode_db_repo
106 106
107 107 if org_repo.scm_instance.alias != 'hg':
108 108 log.error('Review not available for GIT REPOS')
109 109 raise HTTPNotFound
110 110
111 111 try:
112 112 org_repo.scm_instance.get_changeset()
113 113 except EmptyRepositoryError, e:
114 114 h.flash(h.literal(_('There are no changesets yet')),
115 115 category='warning')
116 116 redirect(url('summary_home', repo_name=org_repo.repo_name))
117 117
118 118 other_repos_info = {}
119 119
120 120 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
121 121 c.org_repos = []
122 122 c.other_repos = []
123 123 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
124 124 org_repo.user.username, c.repo_name))
125 125 )
126 126
127 127 # add org repo to other so we can open pull request agains itself
128 128 c.other_repos.extend(c.org_repos)
129 129
130 130 c.default_pull_request = org_repo.repo_name # repo name pre-selected
131 131 c.default_pull_request_rev = self._get_default_rev(org_repo) # revision pre-selected
132 132 c.default_revs = self._get_repo_refs(org_repo.scm_instance)
133 133 #add orginal repo
134 134 other_repos_info[org_repo.repo_name] = {
135 135 'gravatar': h.gravatar_url(org_repo.user.email, 24),
136 136 'description': org_repo.description,
137 137 'revs': h.select('other_ref', '', c.default_revs, class_='refs')
138 138 }
139 139
140 140 #gather forks and add to this list
141 141 for fork in org_repo.forks:
142 142 c.other_repos.append((fork.repo_name, '%s/%s' % (
143 143 fork.user.username, fork.repo_name))
144 144 )
145 145 other_repos_info[fork.repo_name] = {
146 146 'gravatar': h.gravatar_url(fork.user.email, 24),
147 147 'description': fork.description,
148 148 'revs': h.select('other_ref', '',
149 149 self._get_repo_refs(fork.scm_instance),
150 150 class_='refs')
151 151 }
152 152 #add parents of this fork also, but only if it's not empty
153 153 if org_repo.parent and org_repo.parent.scm_instance.revisions:
154 154 c.default_pull_request = org_repo.parent.repo_name
155 155 c.default_pull_request_rev = self._get_default_rev(org_repo.parent)
156 156 c.default_revs = self._get_repo_refs(org_repo.parent.scm_instance)
157 157 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
158 158 org_repo.parent.user.username,
159 159 org_repo.parent.repo_name))
160 160 )
161 161 other_repos_info[org_repo.parent.repo_name] = {
162 162 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
163 163 'description': org_repo.parent.description,
164 164 'revs': h.select('other_ref', '',
165 165 self._get_repo_refs(org_repo.parent.scm_instance),
166 166 class_='refs')
167 167 }
168 168
169 169 c.other_repos_info = json.dumps(other_repos_info)
170 170 c.review_members = [org_repo.user]
171 171 return render('/pullrequests/pullrequest.html')
172 172
173 173 @NotAnonymous()
174 174 def create(self, repo_name):
175 175 repo = RepoModel()._get_repo(repo_name)
176 176 try:
177 177 _form = PullRequestForm(repo.repo_id)().to_python(request.POST)
178 178 except formencode.Invalid, errors:
179 179 log.error(traceback.format_exc())
180 180 if errors.error_dict.get('revisions'):
181 181 msg = 'Revisions: %s' % errors.error_dict['revisions']
182 182 elif errors.error_dict.get('pullrequest_title'):
183 183 msg = _('Pull request requires a title with min. 3 chars')
184 184 else:
185 185 msg = _('error during creation of pull request')
186 186
187 187 h.flash(msg, 'error')
188 188 return redirect(url('pullrequest_home', repo_name=repo_name))
189 189
190 190 org_repo = _form['org_repo']
191 191 org_ref = _form['org_ref']
192 192 other_repo = _form['other_repo']
193 193 other_ref = _form['other_ref']
194 194 revisions = _form['revisions']
195 195 reviewers = _form['review_members']
196 196
197 197 title = _form['pullrequest_title']
198 198 description = _form['pullrequest_desc']
199 199
200 200 try:
201 201 pull_request = PullRequestModel().create(
202 202 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
203 203 other_ref, revisions, reviewers, title, description
204 204 )
205 205 Session().commit()
206 206 h.flash(_('Successfully opened new pull request'),
207 207 category='success')
208 208 except Exception:
209 209 h.flash(_('Error occurred during sending pull request'),
210 210 category='error')
211 211 log.error(traceback.format_exc())
212 212 return redirect(url('pullrequest_home', repo_name=repo_name))
213 213
214 214 return redirect(url('pullrequest_show', repo_name=other_repo,
215 215 pull_request_id=pull_request.pull_request_id))
216 216
217 217 @NotAnonymous()
218 218 @jsonify
219 219 def update(self, repo_name, pull_request_id):
220 220 pull_request = PullRequest.get_or_404(pull_request_id)
221 221 if pull_request.is_closed():
222 222 raise HTTPForbidden()
223 223 #only owner or admin can update it
224 224 owner = pull_request.author.user_id == c.rhodecode_user.user_id
225 225 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
226 226 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
227 227 request.POST.get('reviewers_ids', '').split(',')))
228 228
229 229 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
230 230 Session.commit()
231 231 return True
232 232 raise HTTPForbidden()
233 233
234 234 @NotAnonymous()
235 235 @jsonify
236 236 def delete(self, repo_name, pull_request_id):
237 237 pull_request = PullRequest.get_or_404(pull_request_id)
238 238 #only owner can delete it !
239 239 if pull_request.author.user_id == c.rhodecode_user.user_id:
240 240 PullRequestModel().delete(pull_request)
241 241 Session().commit()
242 242 h.flash(_('Successfully deleted pull request'),
243 243 category='success')
244 244 return redirect(url('admin_settings_my_account'))
245 245 raise HTTPForbidden()
246 246
247 247 def _load_compare_data(self, pull_request, enable_comments=True):
248 248 """
249 249 Load context data needed for generating compare diff
250 250
251 251 :param pull_request:
252 252 :type pull_request:
253 253 """
254 254
255 255 org_repo = pull_request.org_repo
256 256 (org_ref_type,
257 257 org_ref_name,
258 258 org_ref_rev) = pull_request.org_ref.split(':')
259 259
260 260 other_repo = pull_request.other_repo
261 261 (other_ref_type,
262 262 other_ref_name,
263 263 other_ref_rev) = pull_request.other_ref.split(':')
264 264
265 265 # despite opening revisions for bookmarks/branches/tags, we always
266 266 # convert this to rev to prevent changes after book or branch change
267 267 org_ref = ('rev', org_ref_rev)
268 268 other_ref = ('rev', other_ref_rev)
269 269
270 270 c.org_repo = org_repo
271 271 c.other_repo = other_repo
272 272
273 273 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
274 274 org_repo, org_ref, other_repo, other_ref
275 275 )
276 276 if c.cs_ranges:
277 277 # case we want a simple diff without incoming changesets, just
278 278 # for review purposes. Make the diff on the forked repo, with
279 279 # revision that is common ancestor
280 280 other_ref = ('rev', c.cs_ranges[-1].parents[0].raw_id)
281 281 other_repo = org_repo
282 282
283 283 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
284 284 # defines that we need hidden inputs with changesets
285 285 c.as_form = request.GET.get('as_form', False)
286 286
287 287 c.org_ref = org_ref[1]
288 288 c.other_ref = other_ref[1]
289 289 # diff needs to have swapped org with other to generate proper diff
290 290 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
291 291 discovery_data)
292 292 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
293 293 _parsed = diff_processor.prepare()
294 294
295 295 c.files = []
296 296 c.changes = {}
297 297
298 298 for f in _parsed:
299 299 fid = h.FID('', f['filename'])
300 300 c.files.append([fid, f['operation'], f['filename'], f['stats']])
301 301 diff = diff_processor.as_html(enable_comments=enable_comments,
302 diff_lines=[f])
302 parsed_lines=[f])
303 303 c.changes[fid] = [f['operation'], f['filename'], diff]
304 304
305 305 def show(self, repo_name, pull_request_id):
306 306 repo_model = RepoModel()
307 307 c.users_array = repo_model.get_users_js()
308 308 c.users_groups_array = repo_model.get_users_groups_js()
309 309 c.pull_request = PullRequest.get_or_404(pull_request_id)
310 310 c.target_repo = c.pull_request.org_repo.repo_name
311 311
312 312 cc_model = ChangesetCommentsModel()
313 313 cs_model = ChangesetStatusModel()
314 314 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
315 315 pull_request=c.pull_request,
316 316 with_revisions=True)
317 317
318 318 cs_statuses = defaultdict(list)
319 319 for st in _cs_statuses:
320 320 cs_statuses[st.author.username] += [st]
321 321
322 322 c.pull_request_reviewers = []
323 323 c.pull_request_pending_reviewers = []
324 324 for o in c.pull_request.reviewers:
325 325 st = cs_statuses.get(o.user.username, None)
326 326 if st:
327 327 sorter = lambda k: k.version
328 328 st = [(x, list(y)[0])
329 329 for x, y in (groupby(sorted(st, key=sorter), sorter))]
330 330 else:
331 331 c.pull_request_pending_reviewers.append(o.user)
332 332 c.pull_request_reviewers.append([o.user, st])
333 333
334 334 # pull_requests repo_name we opened it against
335 335 # ie. other_repo must match
336 336 if repo_name != c.pull_request.other_repo.repo_name:
337 337 raise HTTPNotFound
338 338
339 339 # load compare data into template context
340 340 enable_comments = not c.pull_request.is_closed()
341 341 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
342 342
343 343 # inline comments
344 344 c.inline_cnt = 0
345 345 c.inline_comments = cc_model.get_inline_comments(
346 346 c.rhodecode_db_repo.repo_id,
347 347 pull_request=pull_request_id)
348 348 # count inline comments
349 349 for __, lines in c.inline_comments:
350 350 for comments in lines.values():
351 351 c.inline_cnt += len(comments)
352 352 # comments
353 353 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
354 354 pull_request=pull_request_id)
355 355
356 356 try:
357 357 cur_status = c.statuses[c.pull_request.revisions[0]][0]
358 358 except:
359 359 log.error(traceback.format_exc())
360 360 cur_status = 'undefined'
361 361 if c.pull_request.is_closed() and 0:
362 362 c.current_changeset_status = cur_status
363 363 else:
364 364 # changeset(pull-request) status calulation based on reviewers
365 365 c.current_changeset_status = cs_model.calculate_status(
366 366 c.pull_request_reviewers,
367 367 )
368 368 c.changeset_statuses = ChangesetStatus.STATUSES
369 369
370 370 return render('/pullrequests/pullrequest_show.html')
371 371
372 372 @NotAnonymous()
373 373 @jsonify
374 374 def comment(self, repo_name, pull_request_id):
375 375 pull_request = PullRequest.get_or_404(pull_request_id)
376 376 if pull_request.is_closed():
377 377 raise HTTPForbidden()
378 378
379 379 status = request.POST.get('changeset_status')
380 380 change_status = request.POST.get('change_changeset_status')
381 381 text = request.POST.get('text')
382 382 if status and change_status:
383 383 text = text or (_('Status change -> %s')
384 384 % ChangesetStatus.get_status_lbl(status))
385 385 comm = ChangesetCommentsModel().create(
386 386 text=text,
387 387 repo=c.rhodecode_db_repo.repo_id,
388 388 user=c.rhodecode_user.user_id,
389 389 pull_request=pull_request_id,
390 390 f_path=request.POST.get('f_path'),
391 391 line_no=request.POST.get('line'),
392 392 status_change=(ChangesetStatus.get_status_lbl(status)
393 393 if status and change_status else None)
394 394 )
395 395
396 396 # get status if set !
397 397 if status and change_status:
398 398 ChangesetStatusModel().set_status(
399 399 c.rhodecode_db_repo.repo_id,
400 400 status,
401 401 c.rhodecode_user.user_id,
402 402 comm,
403 403 pull_request=pull_request_id
404 404 )
405 405 action_logger(self.rhodecode_user,
406 406 'user_commented_pull_request:%s' % pull_request_id,
407 407 c.rhodecode_db_repo, self.ip_addr, self.sa)
408 408
409 409 if request.POST.get('save_close'):
410 410 PullRequestModel().close_pull_request(pull_request_id)
411 411 action_logger(self.rhodecode_user,
412 412 'user_closed_pull_request:%s' % pull_request_id,
413 413 c.rhodecode_db_repo, self.ip_addr, self.sa)
414 414
415 415 Session().commit()
416 416
417 417 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
418 418 return redirect(h.url('pullrequest_show', repo_name=repo_name,
419 419 pull_request_id=pull_request_id))
420 420
421 421 data = {
422 422 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
423 423 }
424 424 if comm:
425 425 c.co = comm
426 426 data.update(comm.get_dict())
427 427 data.update({'rendered_text':
428 428 render('changeset/changeset_comment_block.html')})
429 429
430 430 return data
431 431
432 432 @NotAnonymous()
433 433 @jsonify
434 434 def delete_comment(self, repo_name, comment_id):
435 435 co = ChangesetComment.get(comment_id)
436 436 if co.pull_request.is_closed():
437 437 #don't allow deleting comments on closed pull request
438 438 raise HTTPForbidden()
439 439
440 440 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
441 441 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
442 442 ChangesetCommentsModel().delete(comment=co)
443 443 Session().commit()
444 444 return True
445 445 else:
446 446 raise HTTPForbidden()
@@ -1,647 +1,766 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.diffs
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Set of diffing helpers, previously part of vcs
7 7
8 8
9 9 :created_on: Dec 4, 2011
10 10 :author: marcink
11 11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 12 :original copyright: 2007-2008 by Armin Ronacher
13 13 :license: GPLv3, see COPYING for more details.
14 14 """
15 15 # This program is free software: you can redistribute it and/or modify
16 16 # it under the terms of the GNU General Public License as published by
17 17 # the Free Software Foundation, either version 3 of the License, or
18 18 # (at your option) any later version.
19 19 #
20 20 # This program is distributed in the hope that it will be useful,
21 21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 23 # GNU General Public License for more details.
24 24 #
25 25 # You should have received a copy of the GNU General Public License
26 26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27 27
28 28 import re
29 29 import difflib
30 30 import logging
31 import traceback
31 32
32 33 from itertools import tee, imap
33 34
34 35 from mercurial import patch
35 36 from mercurial.mdiff import diffopts
36 37 from mercurial.bundlerepo import bundlerepository
37 38
38 39 from pylons.i18n.translation import _
39 40
40 41 from rhodecode.lib.compat import BytesIO
41 42 from rhodecode.lib.vcs.utils.hgcompat import localrepo
42 43 from rhodecode.lib.vcs.exceptions import VCSError
43 44 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
44 45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 46 from rhodecode.lib.helpers import escape
46 47 from rhodecode.lib.utils import make_ui
47 48 from rhodecode.lib.utils2 import safe_unicode
48 49
49 50 log = logging.getLogger(__name__)
50 51
51 52
52 53 def wrap_to_table(str_):
53 54 return '''<table class="code-difftable">
54 55 <tr class="line no-comment">
55 56 <td class="lineno new"></td>
56 57 <td class="code no-comment"><pre>%s</pre></td>
57 58 </tr>
58 59 </table>''' % str_
59 60
60 61
61 62 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
62 63 ignore_whitespace=True, line_context=3,
63 64 enable_comments=False):
64 65 """
65 66 returns a wrapped diff into a table, checks for cut_off_limit and presents
66 67 proper message
67 68 """
68 69
69 70 if filenode_old is None:
70 71 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
71 72
72 73 if filenode_old.is_binary or filenode_new.is_binary:
73 74 diff = wrap_to_table(_('binary file'))
74 75 stats = (0, 0)
75 76 size = 0
76 77
77 78 elif cut_off_limit != -1 and (cut_off_limit is None or
78 79 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
79 80
80 81 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
81 82 ignore_whitespace=ignore_whitespace,
82 83 context=line_context)
83 84 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
84 85
85 86 diff = diff_processor.as_html(enable_comments=enable_comments)
86 87 stats = diff_processor.stat()
87 88 size = len(diff or '')
88 89 else:
89 90 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
90 91 'diff menu to display this diff'))
91 92 stats = (0, 0)
92 93 size = 0
93 94 if not diff:
94 95 submodules = filter(lambda o: isinstance(o, SubModuleNode),
95 96 [filenode_new, filenode_old])
96 97 if submodules:
97 98 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
98 99 else:
99 100 diff = wrap_to_table(_('No changes detected'))
100 101
101 102 cs1 = filenode_old.changeset.raw_id
102 103 cs2 = filenode_new.changeset.raw_id
103 104
104 105 return size, cs1, cs2, diff, stats
105 106
106 107
107 108 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
108 109 """
109 110 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
110 111
111 112 :param ignore_whitespace: ignore whitespaces in diff
112 113 """
113 114 # make sure we pass in default context
114 115 context = context or 3
115 116 submodules = filter(lambda o: isinstance(o, SubModuleNode),
116 117 [filenode_new, filenode_old])
117 118 if submodules:
118 119 return ''
119 120
120 121 for filenode in (filenode_old, filenode_new):
121 122 if not isinstance(filenode, FileNode):
122 123 raise VCSError("Given object should be FileNode object, not %s"
123 124 % filenode.__class__)
124 125
125 126 repo = filenode_new.changeset.repository
126 127 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
127 128 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
128 129
129 130 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
130 131 ignore_whitespace, context)
131 132 return vcs_gitdiff
132 133
134 NEW_FILENODE = 1
135 DEL_FILENODE = 2
136 MOD_FILENODE = 3
137 RENAMED_FILENODE = 4
138 CHMOD_FILENODE = 5
139
140
141 class DiffLimitExceeded(Exception):
142 pass
143
144
145 class LimitedDiffContainer(object):
146
147 def __init__(self, diff_limit, cur_diff_size, diff):
148 self.diff = diff
149 self.diff_limit = diff_limit
150 self.cur_diff_size = cur_diff_size
151
152 def __iter__(self):
153 for l in self.diff:
154 yield l
155
133 156
134 157 class DiffProcessor(object):
135 158 """
136 Give it a unified diff and it returns a list of the files that were
159 Give it a unified or git diff and it returns a list of the files that were
137 160 mentioned in the diff together with a dict of meta information that
138 161 can be used to render it in a HTML template.
139 162 """
140 163 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
141 164 _newline_marker = '\\ No newline at end of file\n'
165 _git_header_re = re.compile(r"""
166 #^diff[ ]--git
167 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
168 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
169 ^rename[ ]from[ ](?P<rename_from>\S+)\n
170 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
171 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
172 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
173 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
174 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
175 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
176 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
177 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
178 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
179 """, re.VERBOSE | re.MULTILINE)
180 _hg_header_re = re.compile(r"""
181 #^diff[ ]--git
182 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
183 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
184 (?:^rename[ ]from[ ](?P<rename_from>\S+)\n
185 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
186 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
187 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
188 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
189 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
190 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
191 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
192 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
193 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
194 """, re.VERBOSE | re.MULTILINE)
142 195
143 def __init__(self, diff, differ='diff', format='gitdiff'):
144 """
145 :param diff: a text in diff format or generator
146 :param format: format of diff passed, `udiff` or `gitdiff`
196 def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
147 197 """
148 if isinstance(diff, basestring):
149 diff = [diff]
198 :param diff: a text in diff format
199 :param vcs: type of version controll hg or git
200 :param format: format of diff passed, `udiff` or `gitdiff`
201 :param diff_limit: define the size of diff that is considered "big"
202 based on that parameter cut off will be triggered, set to None
203 to show full diff
204 """
205 if not isinstance(diff, basestring):
206 raise Exception('Diff must be a basestring got %s instead' % type(diff))
150 207
151 self.__udiff = diff
152 self.__format = format
208 self._diff = diff
209 self._format = format
153 210 self.adds = 0
154 211 self.removes = 0
155
156 if isinstance(self.__udiff, basestring):
157 self.lines = iter(self.__udiff.splitlines(1))
212 # calculate diff size
213 self.diff_size = len(diff)
214 self.diff_limit = diff_limit
215 self.cur_diff_size = 0
216 self.parsed = False
217 self.parsed_diff = []
218 self.vcs = vcs
158 219
159 elif self.__format == 'gitdiff':
160 udiff_copy = self.copy_iterator()
161 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
162 else:
163 udiff_copy = self.copy_iterator()
164 self.lines = imap(self.escaper, udiff_copy)
165
166 # Select a differ.
167 if differ == 'difflib':
220 if format == 'gitdiff':
168 221 self.differ = self._highlight_line_difflib
222 self._parser = self._parse_gitdiff
169 223 else:
170 224 self.differ = self._highlight_line_udiff
225 self._parser = self._parse_udiff
171 226
172 def escaper(self, string):
173 return string.replace('&', '&amp;')\
174 .replace('<', '&lt;')\
175 .replace('>', '&gt;')
176
177 def copy_iterator(self):
227 def _copy_iterator(self):
178 228 """
179 229 make a fresh copy of generator, we should not iterate thru
180 230 an original as it's needed for repeating operations on
181 231 this instance of DiffProcessor
182 232 """
183 233 self.__udiff, iterator_copy = tee(self.__udiff)
184 234 return iterator_copy
185 235
186 def _extract_rev(self, line1, line2):
236 def _escaper(self, string):
187 237 """
188 Extract the operation (A/M/D), filename and revision hint from a line.
238 Escaper for diff escapes special chars and checks the diff limit
239
240 :param string:
241 :type string:
189 242 """
190 243
191 try:
192 if line1.startswith('--- ') and line2.startswith('+++ '):
193 l1 = line1[4:].split(None, 1)
194 old_filename = (l1[0].replace('a/', '', 1)
195 if len(l1) >= 1 else None)
196 old_rev = l1[1] if len(l1) == 2 else 'old'
244 self.cur_diff_size += len(string)
197 245
198 l2 = line2[4:].split(None, 1)
199 new_filename = (l2[0].replace('b/', '', 1)
200 if len(l1) >= 1 else None)
201 new_rev = l2[1] if len(l2) == 2 else 'new'
246 # escaper get's iterated on each .next() call and it checks if each
247 # parsed line doesn't exceed the diff limit
248 if self.diff_limit is not None and self.cur_diff_size > self.diff_limit:
249 raise DiffLimitExceeded('Diff Limit Exceeded')
202 250
203 filename = (old_filename
204 if old_filename != '/dev/null' else new_filename)
205
206 operation = 'D' if new_filename == '/dev/null' else None
207 if not operation:
208 operation = 'M' if old_filename != '/dev/null' else 'A'
251 return safe_unicode(string).replace('&', '&amp;')\
252 .replace('<', '&lt;')\
253 .replace('>', '&gt;')
209 254
210 return operation, filename, new_rev, old_rev
211 except (ValueError, IndexError):
212 pass
255 def _line_counter(self, l):
256 """
257 Checks each line and bumps total adds/removes for this diff
213 258
214 return None, None, None, None
215
216 def _parse_gitdiff(self, diffiterator):
217 def line_decoder(l):
259 :param l:
260 """
218 261 if l.startswith('+') and not l.startswith('+++'):
219 262 self.adds += 1
220 263 elif l.startswith('-') and not l.startswith('---'):
221 264 self.removes += 1
222 return safe_unicode(l)
223
224 output = list(diffiterator)
225 size = len(output)
226
227 if size == 2:
228 l = []
229 l.extend([output[0]])
230 l.extend(output[1].splitlines(1))
231 return map(line_decoder, l)
232 elif size == 1:
233 return map(line_decoder, output[0].splitlines(1))
234 elif size == 0:
235 return []
236
237 raise Exception('wrong size of diff %s' % size)
265 return l
238 266
239 267 def _highlight_line_difflib(self, line, next_):
240 268 """
241 269 Highlight inline changes in both lines.
242 270 """
243 271
244 272 if line['action'] == 'del':
245 273 old, new = line, next_
246 274 else:
247 275 old, new = next_, line
248 276
249 277 oldwords = re.split(r'(\W)', old['line'])
250 278 newwords = re.split(r'(\W)', new['line'])
251 279
252 280 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
253 281
254 282 oldfragments, newfragments = [], []
255 283 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
256 284 oldfrag = ''.join(oldwords[i1:i2])
257 285 newfrag = ''.join(newwords[j1:j2])
258 286 if tag != 'equal':
259 287 if oldfrag:
260 288 oldfrag = '<del>%s</del>' % oldfrag
261 289 if newfrag:
262 290 newfrag = '<ins>%s</ins>' % newfrag
263 291 oldfragments.append(oldfrag)
264 292 newfragments.append(newfrag)
265 293
266 294 old['line'] = "".join(oldfragments)
267 295 new['line'] = "".join(newfragments)
268 296
269 297 def _highlight_line_udiff(self, line, next_):
270 298 """
271 299 Highlight inline changes in both lines.
272 300 """
273 301 start = 0
274 302 limit = min(len(line['line']), len(next_['line']))
275 303 while start < limit and line['line'][start] == next_['line'][start]:
276 304 start += 1
277 305 end = -1
278 306 limit -= start
279 307 while -end <= limit and line['line'][end] == next_['line'][end]:
280 308 end -= 1
281 309 end += 1
282 310 if start or end:
283 311 def do(l):
284 312 last = end + len(l['line'])
285 313 if l['action'] == 'add':
286 314 tag = 'ins'
287 315 else:
288 316 tag = 'del'
289 317 l['line'] = '%s<%s>%s</%s>%s' % (
290 318 l['line'][:start],
291 319 tag,
292 320 l['line'][start:last],
293 321 tag,
294 322 l['line'][last:]
295 323 )
296 324 do(line)
297 325 do(next_)
298 326
327 def _get_header(self, diff_chunk):
328 """
329 parses the diff header, and returns parts, and leftover diff
330 parts consists of 14 elements::
331
332 a_path, b_path, similarity_index, rename_from, rename_to,
333 old_mode, new_mode, new_file_mode, deleted_file_mode,
334 a_blob_id, b_blob_id, b_mode, a_file, b_file
335
336 :param diff_chunk:
337 :type diff_chunk:
338 """
339
340 if self.vcs == 'git':
341 match = self._git_header_re.match(diff_chunk)
342 diff = diff_chunk[match.end():]
343 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
344 elif self.vcs == 'hg':
345 match = self._hg_header_re.match(diff_chunk)
346 diff = diff_chunk[match.end():]
347 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
348 else:
349 raise Exception('VCS type %s is not supported' % self.vcs)
350
351 def _parse_gitdiff(self, inline_diff=True):
352 _files = []
353 diff_container = lambda arg: arg
354
355 ##split the diff in chunks of separate --git a/file b/file chunks
356 for raw_diff in ('\n' + self._diff).split('\ndiff --git')[1:]:
357 binary = False
358 binary_msg = 'unknown binary'
359 head, diff = self._get_header(raw_diff)
360
361 if not head['a_file'] and head['b_file']:
362 op = 'A'
363 elif head['a_file'] and head['b_file']:
364 op = 'M'
365 elif head['a_file'] and not head['b_file']:
366 op = 'D'
367 else:
368 #probably we're dealing with a binary file 1
369 binary = True
370 if head['deleted_file_mode']:
371 op = 'D'
372 stats = ['b', DEL_FILENODE]
373 binary_msg = 'deleted binary file'
374 elif head['new_file_mode']:
375 op = 'A'
376 stats = ['b', NEW_FILENODE]
377 binary_msg = 'new binary file %s' % head['new_file_mode']
378 else:
379 if head['new_mode'] and head['old_mode']:
380 stats = ['b', CHMOD_FILENODE]
381 op = 'M'
382 binary_msg = ('modified binary file chmod %s => %s'
383 % (head['old_mode'], head['new_mode']))
384 elif (head['rename_from'] and head['rename_to']
385 and head['rename_from'] != head['rename_to']):
386 stats = ['b', RENAMED_FILENODE]
387 op = 'M'
388 binary_msg = ('file renamed from %s to %s'
389 % (head['rename_from'], head['rename_to']))
390 else:
391 stats = ['b', MOD_FILENODE]
392 op = 'M'
393 binary_msg = 'modified binary file'
394
395 if not binary:
396 try:
397 chunks, stats = self._parse_lines(diff)
398 except DiffLimitExceeded:
399 diff_container = lambda _diff: LimitedDiffContainer(
400 self.diff_limit,
401 self.cur_diff_size,
402 _diff)
403 break
404 else:
405 chunks = []
406 chunks.append([{
407 'old_lineno': '',
408 'new_lineno': '',
409 'action': 'binary',
410 'line': binary_msg,
411 }])
412
413 _files.append({
414 'filename': head['b_path'],
415 'old_revision': head['a_blob_id'],
416 'new_revision': head['b_blob_id'],
417 'chunks': chunks,
418 'operation': op,
419 'stats': stats,
420 })
421
422 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
423
424 if inline_diff is False:
425 return diff_container(sorted(_files, key=sorter))
426
427 # highlight inline changes
428 for diff_data in _files:
429 for chunk in diff_data['chunks']:
430 lineiter = iter(chunk)
431 try:
432 while 1:
433 line = lineiter.next()
434 if line['action'] not in ['unmod', 'context']:
435 nextline = lineiter.next()
436 if nextline['action'] in ['unmod', 'context'] or \
437 nextline['action'] == line['action']:
438 continue
439 self.differ(line, nextline)
440 except StopIteration:
441 pass
442
443 return diff_container(sorted(_files, key=sorter))
444
299 445 def _parse_udiff(self, inline_diff=True):
446 raise NotImplementedError()
447
448 def _parse_lines(self, diff):
300 449 """
301 450 Parse the diff an return data for the template.
302 451 """
303 lineiter = self.lines
304 452
305 files = []
453 lineiter = iter(diff)
454 stats = [0, 0]
455
306 456 try:
307 line = lineiter.next()
308 while 1:
309 # continue until we found the old file
310 if not line.startswith('--- '):
311 line = lineiter.next()
312 continue
313
314 457 chunks = []
315 stats = [0, 0]
316 operation, filename, old_rev, new_rev = \
317 self._extract_rev(line, lineiter.next())
318 files.append({
319 'filename': filename,
320 'old_revision': old_rev,
321 'new_revision': new_rev,
322 'chunks': chunks,
323 'operation': operation,
324 'stats': stats,
325 })
326
327 458 line = lineiter.next()
328 459
329 460 while line:
461 lines = []
462 chunks.append(lines)
463
330 464 match = self._chunk_re.match(line)
465
331 466 if not match:
332 467 break
333 468
334 lines = []
335 chunks.append(lines)
336
337 old_line, old_end, new_line, new_end = \
338 [int(x or 1) for x in match.groups()[:-1]]
469 gr = match.groups()
470 (old_line, old_end,
471 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
339 472 old_line -= 1
340 473 new_line -= 1
341 gr = match.groups()
474
342 475 context = len(gr) == 5
343 476 old_end += old_line
344 477 new_end += new_line
345 478
346 479 if context:
347 480 # skip context only if it's first line
348 481 if int(gr[0]) > 1:
349 482 lines.append({
350 483 'old_lineno': '...',
351 484 'new_lineno': '...',
352 485 'action': 'context',
353 486 'line': line,
354 487 })
355 488
356 489 line = lineiter.next()
357 490
358 491 while old_line < old_end or new_line < new_end:
359 492 if line:
360 493 command = line[0]
361 494 if command in ['+', '-', ' ']:
362 495 #only modify the line if it's actually a diff
363 496 # thing
364 497 line = line[1:]
365 498 else:
366 499 command = ' '
367 500
368 501 affects_old = affects_new = False
369 502
370 503 # ignore those if we don't expect them
371 504 if command in '#@':
372 505 continue
373 506 elif command == '+':
374 507 affects_new = True
375 508 action = 'add'
376 509 stats[0] += 1
377 510 elif command == '-':
378 511 affects_old = True
379 512 action = 'del'
380 513 stats[1] += 1
381 514 else:
382 515 affects_old = affects_new = True
383 516 action = 'unmod'
384 517
385 518 if line != self._newline_marker:
386 519 old_line += affects_old
387 520 new_line += affects_new
388 521 lines.append({
389 522 'old_lineno': affects_old and old_line or '',
390 523 'new_lineno': affects_new and new_line or '',
391 524 'action': action,
392 525 'line': line
393 526 })
394 527
395 528 line = lineiter.next()
529
396 530 if line == self._newline_marker:
397 531 # we need to append to lines, since this is not
398 532 # counted in the line specs of diff
399 533 lines.append({
400 534 'old_lineno': '...',
401 535 'new_lineno': '...',
402 536 'action': 'context',
403 537 'line': line
404 538 })
405 539
406 540 except StopIteration:
407 541 pass
408
409 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
410 if inline_diff is False:
411 return sorted(files, key=sorter)
412
413 # highlight inline changes
414 for diff_data in files:
415 for chunk in diff_data['chunks']:
416 lineiter = iter(chunk)
417 try:
418 while 1:
419 line = lineiter.next()
420 if line['action'] not in ['unmod', 'context']:
421 nextline = lineiter.next()
422 if nextline['action'] in ['unmod', 'context'] or \
423 nextline['action'] == line['action']:
424 continue
425 self.differ(line, nextline)
426 except StopIteration:
427 pass
428
429 return sorted(files, key=sorter)
430
431 def prepare(self, inline_diff=True):
432 """
433 Prepare the passed udiff for HTML rendering. It'l return a list
434 of dicts
435 """
436 return self._parse_udiff(inline_diff=inline_diff)
542 return chunks, stats
437 543
438 544 def _safe_id(self, idstring):
439 545 """Make a string safe for including in an id attribute.
440 546
441 547 The HTML spec says that id attributes 'must begin with
442 548 a letter ([A-Za-z]) and may be followed by any number
443 549 of letters, digits ([0-9]), hyphens ("-"), underscores
444 550 ("_"), colons (":"), and periods (".")'. These regexps
445 551 are slightly over-zealous, in that they remove colons
446 552 and periods unnecessarily.
447 553
448 554 Whitespace is transformed into underscores, and then
449 555 anything which is not a hyphen or a character that
450 556 matches \w (alphanumerics and underscore) is removed.
451 557
452 558 """
453 559 # Transform all whitespace to underscore
454 560 idstring = re.sub(r'\s', "_", '%s' % idstring)
455 561 # Remove everything that is not a hyphen or a member of \w
456 562 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
457 563 return idstring
458 564
459 def raw_diff(self):
565 def prepare(self, inline_diff=True):
566 """
567 Prepare the passed udiff for HTML rendering. It'l return a list
568 of dicts with diff information
569 """
570 parsed = self._parser(inline_diff=inline_diff)
571 self.parsed = True
572 self.parsed_diff = parsed
573 return parsed
574
575 def as_raw(self, diff_lines=None):
460 576 """
461 577 Returns raw string as udiff
462 578 """
463 udiff_copy = self.copy_iterator()
464 if self.__format == 'gitdiff':
465 udiff_copy = self._parse_gitdiff(udiff_copy)
466 return u''.join(udiff_copy)
579 return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
467 580
468 581 def as_html(self, table_class='code-difftable', line_class='line',
469 582 new_lineno_class='lineno old', old_lineno_class='lineno new',
470 code_class='code', enable_comments=False, diff_lines=None):
583 code_class='code', enable_comments=False, parsed_lines=None):
471 584 """
472 585 Return given diff as html table with customized css classes
473 586 """
474 587 def _link_to_if(condition, label, url):
475 588 """
476 589 Generates a link if condition is meet or just the label if not.
477 590 """
478 591
479 592 if condition:
480 593 return '''<a href="%(url)s">%(label)s</a>''' % {
481 594 'url': url,
482 595 'label': label
483 596 }
484 597 else:
485 598 return label
486 if diff_lines is None:
487 diff_lines = self.prepare()
599 if not self.parsed:
600 self.prepare()
601
602 diff_lines = self.parsed_diff
603 if parsed_lines:
604 diff_lines = parsed_lines
605
488 606 _html_empty = True
489 607 _html = []
490 608 _html.append('''<table class="%(table_class)s">\n''' % {
491 609 'table_class': table_class
492 610 })
611
493 612 for diff in diff_lines:
494 613 for line in diff['chunks']:
495 614 _html_empty = False
496 615 for change in line:
497 616 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
498 617 'lc': line_class,
499 618 'action': change['action']
500 619 })
501 620 anchor_old_id = ''
502 621 anchor_new_id = ''
503 622 anchor_old = "%(filename)s_o%(oldline_no)s" % {
504 623 'filename': self._safe_id(diff['filename']),
505 624 'oldline_no': change['old_lineno']
506 625 }
507 626 anchor_new = "%(filename)s_n%(oldline_no)s" % {
508 627 'filename': self._safe_id(diff['filename']),
509 628 'oldline_no': change['new_lineno']
510 629 }
511 630 cond_old = (change['old_lineno'] != '...' and
512 631 change['old_lineno'])
513 632 cond_new = (change['new_lineno'] != '...' and
514 633 change['new_lineno'])
515 634 if cond_old:
516 635 anchor_old_id = 'id="%s"' % anchor_old
517 636 if cond_new:
518 637 anchor_new_id = 'id="%s"' % anchor_new
519 638 ###########################################################
520 639 # OLD LINE NUMBER
521 640 ###########################################################
522 641 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
523 642 'a_id': anchor_old_id,
524 643 'olc': old_lineno_class
525 644 })
526 645
527 646 _html.append('''%(link)s''' % {
528 647 'link': _link_to_if(True, change['old_lineno'],
529 648 '#%s' % anchor_old)
530 649 })
531 650 _html.append('''</td>\n''')
532 651 ###########################################################
533 652 # NEW LINE NUMBER
534 653 ###########################################################
535 654
536 655 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
537 656 'a_id': anchor_new_id,
538 657 'nlc': new_lineno_class
539 658 })
540 659
541 660 _html.append('''%(link)s''' % {
542 661 'link': _link_to_if(True, change['new_lineno'],
543 662 '#%s' % anchor_new)
544 663 })
545 664 _html.append('''</td>\n''')
546 665 ###########################################################
547 666 # CODE
548 667 ###########################################################
549 668 comments = '' if enable_comments else 'no-comment'
550 669 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
551 670 'cc': code_class,
552 671 'inc': comments
553 672 })
554 673 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
555 674 'code': change['line']
556 675 })
557 676
558 677 _html.append('''\t</td>''')
559 678 _html.append('''\n</tr>\n''')
560 679 _html.append('''</table>''')
561 680 if _html_empty:
562 681 return None
563 682 return ''.join(_html)
564 683
565 684 def stat(self):
566 685 """
567 686 Returns tuple of added, and removed lines for this instance
568 687 """
569 688 return self.adds, self.removes
570 689
571 690
572 691 class InMemoryBundleRepo(bundlerepository):
573 692 def __init__(self, ui, path, bundlestream):
574 693 self._tempparent = None
575 694 localrepo.localrepository.__init__(self, ui, path)
576 695 self.ui.setconfig('phases', 'publish', False)
577 696
578 697 self.bundle = bundlestream
579 698
580 699 # dict with the mapping 'filename' -> position in the bundle
581 700 self.bundlefilespos = {}
582 701
583 702
584 703 def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None,
585 bundle_compare=False):
704 bundle_compare=False, context=3, ignore_whitespace=False):
586 705 """
587 General differ between branches, bookmarks or separate but releated
706 General differ between branches, bookmarks, revisions of two remote related
588 707 repositories
589 708
590 709 :param org_repo:
591 710 :type org_repo:
592 711 :param org_ref:
593 712 :type org_ref:
594 713 :param other_repo:
595 714 :type other_repo:
596 715 :param other_ref:
597 716 :type other_ref:
598 717 """
599 718
600 719 bundlerepo = None
601 ignore_whitespace = False
602 context = 3
720 ignore_whitespace = ignore_whitespace
721 context = context
603 722 org_repo = org_repo.scm_instance._repo
604 723 other_repo = other_repo.scm_instance._repo
605 724 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
606 725 org_ref = org_ref[1]
607 726 other_ref = other_ref[1]
608 727
609 728 if org_repo != other_repo and bundle_compare:
610 729
611 730 common, incoming, rheads = discovery_data
612 731 other_repo_peer = localrepo.locallegacypeer(other_repo.local())
613 732 # create a bundle (uncompressed if other repo is not local)
614 733 if other_repo_peer.capable('getbundle') and incoming:
615 734 # disable repo hooks here since it's just bundle !
616 735 # patch and reset hooks section of UI config to not run any
617 736 # hooks on fetching archives with subrepos
618 737 for k, _ in other_repo.ui.configitems('hooks'):
619 738 other_repo.ui.setconfig('hooks', k, None)
620 739
621 740 unbundle = other_repo.getbundle('incoming', common=common,
622 741 heads=None)
623 742
624 743 buf = BytesIO()
625 744 while True:
626 745 chunk = unbundle._stream.read(1024 * 4)
627 746 if not chunk:
628 747 break
629 748 buf.write(chunk)
630 749
631 750 buf.seek(0)
632 751 # replace chunked _stream with data that can do tell() and seek()
633 752 unbundle._stream = buf
634 753
635 754 ui = make_ui('db')
636 755 bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
637 756 bundlestream=unbundle)
638 757
639 758 return ''.join(patch.diff(bundlerepo or org_repo,
640 759 node1=org_repo[org_ref].node(),
641 760 node2=other_repo[other_ref].node(),
642 761 opts=opts))
643 762 else:
644 763 log.debug('running diff between %s@%s and %s@%s'
645 764 % (org_repo, org_ref, other_repo, other_ref))
646 765 return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref,
647 766 opts=opts))
@@ -1,1104 +1,1111 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 import StringIO
9 9 import urllib
10 10 import math
11 11 import logging
12 12 import re
13 13 import urlparse
14 14
15 15 from datetime import datetime
16 16 from pygments.formatters.html import HtmlFormatter
17 17 from pygments import highlight as code_highlight
18 18 from pylons import url, request, config
19 19 from pylons.i18n.translation import _, ungettext
20 20 from hashlib import md5
21 21
22 22 from webhelpers.html import literal, HTML, escape
23 23 from webhelpers.html.tools import *
24 24 from webhelpers.html.builder import make_tag
25 25 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
26 26 end_form, file, form, hidden, image, javascript_link, link_to, \
27 27 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
28 28 submit, text, password, textarea, title, ul, xml_declaration, radio
29 29 from webhelpers.html.tools import auto_link, button_to, highlight, \
30 30 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
31 31 from webhelpers.number import format_byte_size, format_bit_size
32 32 from webhelpers.pylonslib import Flash as _Flash
33 33 from webhelpers.pylonslib.secure_form import secure_form
34 34 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
35 35 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
36 36 replace_whitespace, urlify, truncate, wrap_paragraphs
37 37 from webhelpers.date import time_ago_in_words
38 38 from webhelpers.paginate import Page
39 39 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
40 40 convert_boolean_attrs, NotGiven, _make_safe_id_component
41 41
42 42 from rhodecode.lib.annotate import annotate_highlight
43 43 from rhodecode.lib.utils import repo_name_slug
44 44 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
45 45 get_changeset_safe, datetime_to_time, time_to_datetime, AttributeDict
46 46 from rhodecode.lib.markup_renderer import MarkupRenderer
47 47 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
48 48 from rhodecode.lib.vcs.backends.base import BaseChangeset
49 49 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
50 50 from rhodecode.model.changeset_status import ChangesetStatusModel
51 51 from rhodecode.model.db import URL_SEP, Permission
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 html_escape_table = {
57 57 "&": "&amp;",
58 58 '"': "&quot;",
59 59 "'": "&apos;",
60 60 ">": "&gt;",
61 61 "<": "&lt;",
62 62 }
63 63
64 64
65 65 def html_escape(text):
66 66 """Produce entities within text."""
67 67 return "".join(html_escape_table.get(c, c) for c in text)
68 68
69 69
70 70 def shorter(text, size=20):
71 71 postfix = '...'
72 72 if len(text) > size:
73 73 return text[:size - len(postfix)] + postfix
74 74 return text
75 75
76 76
77 77 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
78 78 """
79 79 Reset button
80 80 """
81 81 _set_input_attrs(attrs, type, name, value)
82 82 _set_id_attr(attrs, id, name)
83 83 convert_boolean_attrs(attrs, ["disabled"])
84 84 return HTML.input(**attrs)
85 85
86 86 reset = _reset
87 87 safeid = _make_safe_id_component
88 88
89 89
90 90 def FID(raw_id, path):
91 91 """
92 92 Creates a uniqe ID for filenode based on it's hash of path and revision
93 93 it's safe to use in urls
94 94
95 95 :param raw_id:
96 96 :param path:
97 97 """
98 98
99 99 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
100 100
101 101
102 102 def get_token():
103 103 """Return the current authentication token, creating one if one doesn't
104 104 already exist.
105 105 """
106 106 token_key = "_authentication_token"
107 107 from pylons import session
108 108 if not token_key in session:
109 109 try:
110 110 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
111 111 except AttributeError: # Python < 2.4
112 112 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
113 113 session[token_key] = token
114 114 if hasattr(session, 'save'):
115 115 session.save()
116 116 return session[token_key]
117 117
118 118
119 119 class _GetError(object):
120 120 """Get error from form_errors, and represent it as span wrapped error
121 121 message
122 122
123 123 :param field_name: field to fetch errors for
124 124 :param form_errors: form errors dict
125 125 """
126 126
127 127 def __call__(self, field_name, form_errors):
128 128 tmpl = """<span class="error_msg">%s</span>"""
129 129 if form_errors and field_name in form_errors:
130 130 return literal(tmpl % form_errors.get(field_name))
131 131
132 132 get_error = _GetError()
133 133
134 134
135 135 class _ToolTip(object):
136 136
137 137 def __call__(self, tooltip_title, trim_at=50):
138 138 """
139 139 Special function just to wrap our text into nice formatted
140 140 autowrapped text
141 141
142 142 :param tooltip_title:
143 143 """
144 144 tooltip_title = escape(tooltip_title)
145 145 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
146 146 return tooltip_title
147 147 tooltip = _ToolTip()
148 148
149 149
150 150 class _FilesBreadCrumbs(object):
151 151
152 152 def __call__(self, repo_name, rev, paths):
153 153 if isinstance(paths, str):
154 154 paths = safe_unicode(paths)
155 155 url_l = [link_to(repo_name, url('files_home',
156 156 repo_name=repo_name,
157 157 revision=rev, f_path=''),
158 158 class_='ypjax-link')]
159 159 paths_l = paths.split('/')
160 160 for cnt, p in enumerate(paths_l):
161 161 if p != '':
162 162 url_l.append(link_to(p,
163 163 url('files_home',
164 164 repo_name=repo_name,
165 165 revision=rev,
166 166 f_path='/'.join(paths_l[:cnt + 1])
167 167 ),
168 168 class_='ypjax-link'
169 169 )
170 170 )
171 171
172 172 return literal('/'.join(url_l))
173 173
174 174 files_breadcrumbs = _FilesBreadCrumbs()
175 175
176 176
177 177 class CodeHtmlFormatter(HtmlFormatter):
178 178 """
179 179 My code Html Formatter for source codes
180 180 """
181 181
182 182 def wrap(self, source, outfile):
183 183 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
184 184
185 185 def _wrap_code(self, source):
186 186 for cnt, it in enumerate(source):
187 187 i, t = it
188 188 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
189 189 yield i, t
190 190
191 191 def _wrap_tablelinenos(self, inner):
192 192 dummyoutfile = StringIO.StringIO()
193 193 lncount = 0
194 194 for t, line in inner:
195 195 if t:
196 196 lncount += 1
197 197 dummyoutfile.write(line)
198 198
199 199 fl = self.linenostart
200 200 mw = len(str(lncount + fl - 1))
201 201 sp = self.linenospecial
202 202 st = self.linenostep
203 203 la = self.lineanchors
204 204 aln = self.anchorlinenos
205 205 nocls = self.noclasses
206 206 if sp:
207 207 lines = []
208 208
209 209 for i in range(fl, fl + lncount):
210 210 if i % st == 0:
211 211 if i % sp == 0:
212 212 if aln:
213 213 lines.append('<a href="#%s%d" class="special">%*d</a>' %
214 214 (la, i, mw, i))
215 215 else:
216 216 lines.append('<span class="special">%*d</span>' % (mw, i))
217 217 else:
218 218 if aln:
219 219 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
220 220 else:
221 221 lines.append('%*d' % (mw, i))
222 222 else:
223 223 lines.append('')
224 224 ls = '\n'.join(lines)
225 225 else:
226 226 lines = []
227 227 for i in range(fl, fl + lncount):
228 228 if i % st == 0:
229 229 if aln:
230 230 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
231 231 else:
232 232 lines.append('%*d' % (mw, i))
233 233 else:
234 234 lines.append('')
235 235 ls = '\n'.join(lines)
236 236
237 237 # in case you wonder about the seemingly redundant <div> here: since the
238 238 # content in the other cell also is wrapped in a div, some browsers in
239 239 # some configurations seem to mess up the formatting...
240 240 if nocls:
241 241 yield 0, ('<table class="%stable">' % self.cssclass +
242 242 '<tr><td><div class="linenodiv" '
243 243 'style="background-color: #f0f0f0; padding-right: 10px">'
244 244 '<pre style="line-height: 125%">' +
245 245 ls + '</pre></div></td><td id="hlcode" class="code">')
246 246 else:
247 247 yield 0, ('<table class="%stable">' % self.cssclass +
248 248 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
249 249 ls + '</pre></div></td><td id="hlcode" class="code">')
250 250 yield 0, dummyoutfile.getvalue()
251 251 yield 0, '</td></tr></table>'
252 252
253 253
254 254 def pygmentize(filenode, **kwargs):
255 255 """pygmentize function using pygments
256 256
257 257 :param filenode:
258 258 """
259 259
260 260 return literal(code_highlight(filenode.content,
261 261 filenode.lexer, CodeHtmlFormatter(**kwargs)))
262 262
263 263
264 264 def pygmentize_annotation(repo_name, filenode, **kwargs):
265 265 """
266 266 pygmentize function for annotation
267 267
268 268 :param filenode:
269 269 """
270 270
271 271 color_dict = {}
272 272
273 273 def gen_color(n=10000):
274 274 """generator for getting n of evenly distributed colors using
275 275 hsv color and golden ratio. It always return same order of colors
276 276
277 277 :returns: RGB tuple
278 278 """
279 279
280 280 def hsv_to_rgb(h, s, v):
281 281 if s == 0.0:
282 282 return v, v, v
283 283 i = int(h * 6.0) # XXX assume int() truncates!
284 284 f = (h * 6.0) - i
285 285 p = v * (1.0 - s)
286 286 q = v * (1.0 - s * f)
287 287 t = v * (1.0 - s * (1.0 - f))
288 288 i = i % 6
289 289 if i == 0:
290 290 return v, t, p
291 291 if i == 1:
292 292 return q, v, p
293 293 if i == 2:
294 294 return p, v, t
295 295 if i == 3:
296 296 return p, q, v
297 297 if i == 4:
298 298 return t, p, v
299 299 if i == 5:
300 300 return v, p, q
301 301
302 302 golden_ratio = 0.618033988749895
303 303 h = 0.22717784590367374
304 304
305 305 for _ in xrange(n):
306 306 h += golden_ratio
307 307 h %= 1
308 308 HSV_tuple = [h, 0.95, 0.95]
309 309 RGB_tuple = hsv_to_rgb(*HSV_tuple)
310 310 yield map(lambda x: str(int(x * 256)), RGB_tuple)
311 311
312 312 cgenerator = gen_color()
313 313
314 314 def get_color_string(cs):
315 315 if cs in color_dict:
316 316 col = color_dict[cs]
317 317 else:
318 318 col = color_dict[cs] = cgenerator.next()
319 319 return "color: rgb(%s)! important;" % (', '.join(col))
320 320
321 321 def url_func(repo_name):
322 322
323 323 def _url_func(changeset):
324 324 author = changeset.author
325 325 date = changeset.date
326 326 message = tooltip(changeset.message)
327 327
328 328 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
329 329 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
330 330 "</b> %s<br/></div>")
331 331
332 332 tooltip_html = tooltip_html % (author, date, message)
333 333 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
334 334 short_id(changeset.raw_id))
335 335 uri = link_to(
336 336 lnk_format,
337 337 url('changeset_home', repo_name=repo_name,
338 338 revision=changeset.raw_id),
339 339 style=get_color_string(changeset.raw_id),
340 340 class_='tooltip',
341 341 title=tooltip_html
342 342 )
343 343
344 344 uri += '\n'
345 345 return uri
346 346 return _url_func
347 347
348 348 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
349 349
350 350
351 351 def is_following_repo(repo_name, user_id):
352 352 from rhodecode.model.scm import ScmModel
353 353 return ScmModel().is_following_repo(repo_name, user_id)
354 354
355 355 flash = _Flash()
356 356
357 357 #==============================================================================
358 358 # SCM FILTERS available via h.
359 359 #==============================================================================
360 360 from rhodecode.lib.vcs.utils import author_name, author_email
361 361 from rhodecode.lib.utils2 import credentials_filter, age as _age
362 362 from rhodecode.model.db import User, ChangesetStatus
363 363
364 364 age = lambda x: _age(x)
365 365 capitalize = lambda x: x.capitalize()
366 366 email = author_email
367 367 short_id = lambda x: x[:12]
368 368 hide_credentials = lambda x: ''.join(credentials_filter(x))
369 369
370 370
371 371 def fmt_date(date):
372 372 if date:
373 373 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
374 374 return date.strftime(_fmt).decode('utf8')
375 375
376 376 return ""
377 377
378 378
379 379 def is_git(repository):
380 380 if hasattr(repository, 'alias'):
381 381 _type = repository.alias
382 382 elif hasattr(repository, 'repo_type'):
383 383 _type = repository.repo_type
384 384 else:
385 385 _type = repository
386 386 return _type == 'git'
387 387
388 388
389 389 def is_hg(repository):
390 390 if hasattr(repository, 'alias'):
391 391 _type = repository.alias
392 392 elif hasattr(repository, 'repo_type'):
393 393 _type = repository.repo_type
394 394 else:
395 395 _type = repository
396 396 return _type == 'hg'
397 397
398 398
399 399 def email_or_none(author):
400 400 # extract email from the commit string
401 401 _email = email(author)
402 402 if _email != '':
403 403 # check it against RhodeCode database, and use the MAIN email for this
404 404 # user
405 405 user = User.get_by_email(_email, case_insensitive=True, cache=True)
406 406 if user is not None:
407 407 return user.email
408 408 return _email
409 409
410 410 # See if it contains a username we can get an email from
411 411 user = User.get_by_username(author_name(author), case_insensitive=True,
412 412 cache=True)
413 413 if user is not None:
414 414 return user.email
415 415
416 416 # No valid email, not a valid user in the system, none!
417 417 return None
418 418
419 419
420 420 def person(author, show_attr="username_and_name"):
421 421 # attr to return from fetched user
422 422 person_getter = lambda usr: getattr(usr, show_attr)
423 423
424 424 # Valid email in the attribute passed, see if they're in the system
425 425 _email = email(author)
426 426 if _email != '':
427 427 user = User.get_by_email(_email, case_insensitive=True, cache=True)
428 428 if user is not None:
429 429 return person_getter(user)
430 430 return _email
431 431
432 432 # Maybe it's a username?
433 433 _author = author_name(author)
434 434 user = User.get_by_username(_author, case_insensitive=True,
435 435 cache=True)
436 436 if user is not None:
437 437 return person_getter(user)
438 438
439 439 # Still nothing? Just pass back the author name then
440 440 return _author
441 441
442 442
443 443 def person_by_id(id_, show_attr="username_and_name"):
444 444 # attr to return from fetched user
445 445 person_getter = lambda usr: getattr(usr, show_attr)
446 446
447 447 #maybe it's an ID ?
448 448 if str(id_).isdigit() or isinstance(id_, int):
449 449 id_ = int(id_)
450 450 user = User.get(id_)
451 451 if user is not None:
452 452 return person_getter(user)
453 453 return id_
454 454
455 455
456 456 def desc_stylize(value):
457 457 """
458 458 converts tags from value into html equivalent
459 459
460 460 :param value:
461 461 """
462 462 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
463 463 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
464 464 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
465 465 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
466 466 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
467 467 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
468 468 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
469 469 '<div class="metatag" tag="lang">\\2</div>', value)
470 470 value = re.sub(r'\[([a-z]+)\]',
471 471 '<div class="metatag" tag="\\1">\\1</div>', value)
472 472
473 473 return value
474 474
475 475
476 476 def bool2icon(value):
477 477 """Returns True/False values represented as small html image of true/false
478 478 icons
479 479
480 480 :param value: bool value
481 481 """
482 482
483 483 if value is True:
484 484 return HTML.tag('img', src=url("/images/icons/accept.png"),
485 485 alt=_('True'))
486 486
487 487 if value is False:
488 488 return HTML.tag('img', src=url("/images/icons/cancel.png"),
489 489 alt=_('False'))
490 490
491 491 return value
492 492
493 493
494 494 def action_parser(user_log, feed=False, parse_cs=False):
495 495 """
496 496 This helper will action_map the specified string action into translated
497 497 fancy names with icons and links
498 498
499 499 :param user_log: user log instance
500 500 :param feed: use output for feeds (no html and fancy icons)
501 501 :param parse_cs: parse Changesets into VCS instances
502 502 """
503 503
504 504 action = user_log.action
505 505 action_params = ' '
506 506
507 507 x = action.split(':')
508 508
509 509 if len(x) > 1:
510 510 action, action_params = x
511 511
512 512 def get_cs_links():
513 513 revs_limit = 3 # display this amount always
514 514 revs_top_limit = 50 # show upto this amount of changesets hidden
515 515 revs_ids = action_params.split(',')
516 516 deleted = user_log.repository is None
517 517 if deleted:
518 518 return ','.join(revs_ids)
519 519
520 520 repo_name = user_log.repository.repo_name
521 521
522 522 def lnk(rev, repo_name):
523 523 if isinstance(rev, BaseChangeset) or isinstance(rev, AttributeDict):
524 524 lbl = '%s' % (rev.short_id[:8])
525 525 _url = url('changeset_home', repo_name=repo_name,
526 526 revision=rev.raw_id)
527 527 title = tooltip(rev.message)
528 528 else:
529 529 ## changeset cannot be found/striped/removed etc.
530 530 lbl = ('%s' % rev)[:12]
531 531 _url = '#'
532 532 title = _('Changeset not found')
533 533 if parse_cs:
534 534 return link_to(lbl, _url, title=title, class_='tooltip')
535 535 return link_to(lbl, _url, raw_id=rev.raw_id, repo_name=repo_name,
536 536 class_='lazy-cs')
537 537
538 538 revs = []
539 539 if len(filter(lambda v: v != '', revs_ids)) > 0:
540 540 repo = None
541 541 for rev in revs_ids[:revs_top_limit]:
542 542 # we want parsed changesets, or new log store format is bad
543 543 if parse_cs:
544 544 try:
545 545 if repo is None:
546 546 repo = user_log.repository.scm_instance
547 547 _rev = repo.get_changeset(rev)
548 548 revs.append(_rev)
549 549 except ChangesetDoesNotExistError:
550 550 log.error('cannot find revision %s in this repo' % rev)
551 551 revs.append(rev)
552 552 continue
553 553 else:
554 554 _rev = AttributeDict({
555 555 'short_id': rev[:12],
556 556 'raw_id': rev,
557 557 'message': '',
558 558 })
559 559 revs.append(_rev)
560 560 cs_links = []
561 561 cs_links.append(" " + ', '.join(
562 562 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
563 563 )
564 564 )
565 565
566 566 compare_view = (
567 567 ' <div class="compare_view tooltip" title="%s">'
568 568 '<a href="%s">%s</a> </div>' % (
569 569 _('Show all combined changesets %s->%s') % (
570 570 revs_ids[0][:12], revs_ids[-1][:12]
571 571 ),
572 572 url('changeset_home', repo_name=repo_name,
573 573 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
574 574 ),
575 575 _('compare view')
576 576 )
577 577 )
578 578
579 579 # if we have exactly one more than normally displayed
580 580 # just display it, takes less space than displaying
581 581 # "and 1 more revisions"
582 582 if len(revs_ids) == revs_limit + 1:
583 583 rev = revs[revs_limit]
584 584 cs_links.append(", " + lnk(rev, repo_name))
585 585
586 586 # hidden-by-default ones
587 587 if len(revs_ids) > revs_limit + 1:
588 588 uniq_id = revs_ids[0]
589 589 html_tmpl = (
590 590 '<span> %s <a class="show_more" id="_%s" '
591 591 'href="#more">%s</a> %s</span>'
592 592 )
593 593 if not feed:
594 594 cs_links.append(html_tmpl % (
595 595 _('and'),
596 596 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
597 597 _('revisions')
598 598 )
599 599 )
600 600
601 601 if not feed:
602 602 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
603 603 else:
604 604 html_tmpl = '<span id="%s"> %s </span>'
605 605
606 606 morelinks = ', '.join(
607 607 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
608 608 )
609 609
610 610 if len(revs_ids) > revs_top_limit:
611 611 morelinks += ', ...'
612 612
613 613 cs_links.append(html_tmpl % (uniq_id, morelinks))
614 614 if len(revs) > 1:
615 615 cs_links.append(compare_view)
616 616 return ''.join(cs_links)
617 617
618 618 def get_fork_name():
619 619 repo_name = action_params
620 620 _url = url('summary_home', repo_name=repo_name)
621 621 return _('fork name %s') % link_to(action_params, _url)
622 622
623 623 def get_user_name():
624 624 user_name = action_params
625 625 return user_name
626 626
627 627 def get_users_group():
628 628 group_name = action_params
629 629 return group_name
630 630
631 631 def get_pull_request():
632 632 pull_request_id = action_params
633 633 repo_name = user_log.repository.repo_name
634 634 return link_to(_('Pull request #%s') % pull_request_id,
635 635 url('pullrequest_show', repo_name=repo_name,
636 636 pull_request_id=pull_request_id))
637 637
638 638 # action : translated str, callback(extractor), icon
639 639 action_map = {
640 640 'user_deleted_repo': (_('[deleted] repository'),
641 641 None, 'database_delete.png'),
642 642 'user_created_repo': (_('[created] repository'),
643 643 None, 'database_add.png'),
644 644 'user_created_fork': (_('[created] repository as fork'),
645 645 None, 'arrow_divide.png'),
646 646 'user_forked_repo': (_('[forked] repository'),
647 647 get_fork_name, 'arrow_divide.png'),
648 648 'user_updated_repo': (_('[updated] repository'),
649 649 None, 'database_edit.png'),
650 650 'admin_deleted_repo': (_('[delete] repository'),
651 651 None, 'database_delete.png'),
652 652 'admin_created_repo': (_('[created] repository'),
653 653 None, 'database_add.png'),
654 654 'admin_forked_repo': (_('[forked] repository'),
655 655 None, 'arrow_divide.png'),
656 656 'admin_updated_repo': (_('[updated] repository'),
657 657 None, 'database_edit.png'),
658 658 'admin_created_user': (_('[created] user'),
659 659 get_user_name, 'user_add.png'),
660 660 'admin_updated_user': (_('[updated] user'),
661 661 get_user_name, 'user_edit.png'),
662 662 'admin_created_users_group': (_('[created] users group'),
663 663 get_users_group, 'group_add.png'),
664 664 'admin_updated_users_group': (_('[updated] users group'),
665 665 get_users_group, 'group_edit.png'),
666 666 'user_commented_revision': (_('[commented] on revision in repository'),
667 667 get_cs_links, 'comment_add.png'),
668 668 'user_commented_pull_request': (_('[commented] on pull request for'),
669 669 get_pull_request, 'comment_add.png'),
670 670 'user_closed_pull_request': (_('[closed] pull request for'),
671 671 get_pull_request, 'tick.png'),
672 672 'push': (_('[pushed] into'),
673 673 get_cs_links, 'script_add.png'),
674 674 'push_local': (_('[committed via RhodeCode] into repository'),
675 675 get_cs_links, 'script_edit.png'),
676 676 'push_remote': (_('[pulled from remote] into repository'),
677 677 get_cs_links, 'connect.png'),
678 678 'pull': (_('[pulled] from'),
679 679 None, 'down_16.png'),
680 680 'started_following_repo': (_('[started following] repository'),
681 681 None, 'heart_add.png'),
682 682 'stopped_following_repo': (_('[stopped following] repository'),
683 683 None, 'heart_delete.png'),
684 684 }
685 685
686 686 action_str = action_map.get(action, action)
687 687 if feed:
688 688 action = action_str[0].replace('[', '').replace(']', '')
689 689 else:
690 690 action = action_str[0]\
691 691 .replace('[', '<span class="journal_highlight">')\
692 692 .replace(']', '</span>')
693 693
694 694 action_params_func = lambda: ""
695 695
696 696 if callable(action_str[1]):
697 697 action_params_func = action_str[1]
698 698
699 699 def action_parser_icon():
700 700 action = user_log.action
701 701 action_params = None
702 702 x = action.split(':')
703 703
704 704 if len(x) > 1:
705 705 action, action_params = x
706 706
707 707 tmpl = """<img src="%s%s" alt="%s"/>"""
708 708 ico = action_map.get(action, ['', '', ''])[2]
709 709 return literal(tmpl % ((url('/images/icons/')), ico, action))
710 710
711 711 # returned callbacks we need to call to get
712 712 return [lambda: literal(action), action_params_func, action_parser_icon]
713 713
714 714
715 715
716 716 #==============================================================================
717 717 # PERMS
718 718 #==============================================================================
719 719 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
720 720 HasRepoPermissionAny, HasRepoPermissionAll
721 721
722 722
723 723 #==============================================================================
724 724 # GRAVATAR URL
725 725 #==============================================================================
726 726
727 727 def gravatar_url(email_address, size=30):
728 728 from pylons import url ## doh, we need to re-import url to mock it later
729 729 if(str2bool(config['app_conf'].get('use_gravatar')) and
730 730 config['app_conf'].get('alternative_gravatar_url')):
731 731 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
732 732 parsed_url = urlparse.urlparse(url.current(qualified=True))
733 733 tmpl = tmpl.replace('{email}', email_address)\
734 734 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
735 735 .replace('{netloc}', parsed_url.netloc)\
736 736 .replace('{scheme}', parsed_url.scheme)\
737 737 .replace('{size}', str(size))
738 738 return tmpl
739 739
740 740 if (not str2bool(config['app_conf'].get('use_gravatar')) or
741 741 not email_address or email_address == 'anonymous@rhodecode.org'):
742 742 f = lambda a, l: min(l, key=lambda x: abs(x - a))
743 743 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
744 744
745 745 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
746 746 default = 'identicon'
747 747 baseurl_nossl = "http://www.gravatar.com/avatar/"
748 748 baseurl_ssl = "https://secure.gravatar.com/avatar/"
749 749 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
750 750
751 751 if isinstance(email_address, unicode):
752 752 #hashlib crashes on unicode items
753 753 email_address = safe_str(email_address)
754 754 # construct the url
755 755 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
756 756 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
757 757
758 758 return gravatar_url
759 759
760 760
761 761 #==============================================================================
762 762 # REPO PAGER, PAGER FOR REPOSITORY
763 763 #==============================================================================
764 764 class RepoPage(Page):
765 765
766 766 def __init__(self, collection, page=1, items_per_page=20,
767 767 item_count=None, url=None, **kwargs):
768 768
769 769 """Create a "RepoPage" instance. special pager for paging
770 770 repository
771 771 """
772 772 self._url_generator = url
773 773
774 774 # Safe the kwargs class-wide so they can be used in the pager() method
775 775 self.kwargs = kwargs
776 776
777 777 # Save a reference to the collection
778 778 self.original_collection = collection
779 779
780 780 self.collection = collection
781 781
782 782 # The self.page is the number of the current page.
783 783 # The first page has the number 1!
784 784 try:
785 785 self.page = int(page) # make it int() if we get it as a string
786 786 except (ValueError, TypeError):
787 787 self.page = 1
788 788
789 789 self.items_per_page = items_per_page
790 790
791 791 # Unless the user tells us how many items the collections has
792 792 # we calculate that ourselves.
793 793 if item_count is not None:
794 794 self.item_count = item_count
795 795 else:
796 796 self.item_count = len(self.collection)
797 797
798 798 # Compute the number of the first and last available page
799 799 if self.item_count > 0:
800 800 self.first_page = 1
801 801 self.page_count = int(math.ceil(float(self.item_count) /
802 802 self.items_per_page))
803 803 self.last_page = self.first_page + self.page_count - 1
804 804
805 805 # Make sure that the requested page number is the range of
806 806 # valid pages
807 807 if self.page > self.last_page:
808 808 self.page = self.last_page
809 809 elif self.page < self.first_page:
810 810 self.page = self.first_page
811 811
812 812 # Note: the number of items on this page can be less than
813 813 # items_per_page if the last page is not full
814 814 self.first_item = max(0, (self.item_count) - (self.page *
815 815 items_per_page))
816 816 self.last_item = ((self.item_count - 1) - items_per_page *
817 817 (self.page - 1))
818 818
819 819 self.items = list(self.collection[self.first_item:self.last_item + 1])
820 820
821 821 # Links to previous and next page
822 822 if self.page > self.first_page:
823 823 self.previous_page = self.page - 1
824 824 else:
825 825 self.previous_page = None
826 826
827 827 if self.page < self.last_page:
828 828 self.next_page = self.page + 1
829 829 else:
830 830 self.next_page = None
831 831
832 832 # No items available
833 833 else:
834 834 self.first_page = None
835 835 self.page_count = 0
836 836 self.last_page = None
837 837 self.first_item = None
838 838 self.last_item = None
839 839 self.previous_page = None
840 840 self.next_page = None
841 841 self.items = []
842 842
843 843 # This is a subclass of the 'list' type. Initialise the list now.
844 844 list.__init__(self, reversed(self.items))
845 845
846 846
847 847 def changed_tooltip(nodes):
848 848 """
849 849 Generates a html string for changed nodes in changeset page.
850 850 It limits the output to 30 entries
851 851
852 852 :param nodes: LazyNodesGenerator
853 853 """
854 854 if nodes:
855 855 pref = ': <br/> '
856 856 suf = ''
857 857 if len(nodes) > 30:
858 858 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
859 859 return literal(pref + '<br/> '.join([safe_unicode(x.path)
860 860 for x in nodes[:30]]) + suf)
861 861 else:
862 862 return ': ' + _('No Files')
863 863
864 864
865 865 def repo_link(groups_and_repos):
866 866 """
867 867 Makes a breadcrumbs link to repo within a group
868 868 joins &raquo; on each group to create a fancy link
869 869
870 870 ex::
871 871 group >> subgroup >> repo
872 872
873 873 :param groups_and_repos:
874 874 """
875 875 groups, repo_name = groups_and_repos
876 876
877 877 if not groups:
878 878 return repo_name
879 879 else:
880 880 def make_link(group):
881 881 return link_to(group.name, url('repos_group_home',
882 882 group_name=group.group_name))
883 883 return literal(' &raquo; '.join(map(make_link, groups)) + \
884 884 " &raquo; " + repo_name)
885 885
886 886
887 887 def fancy_file_stats(stats):
888 888 """
889 889 Displays a fancy two colored bar for number of added/deleted
890 890 lines of code on file
891 891
892 892 :param stats: two element list of added/deleted lines of code
893 893 """
894
895 a, d, t = stats[0], stats[1], stats[0] + stats[1]
896 width = 100
897 unit = float(width) / (t or 1)
898
899 # needs > 9% of width to be visible or 0 to be hidden
900 a_p = max(9, unit * a) if a > 0 else 0
901 d_p = max(9, unit * d) if d > 0 else 0
902 p_sum = a_p + d_p
903
904 if p_sum > width:
905 #adjust the percentage to be == 100% since we adjusted to 9
906 if a_p > d_p:
907 a_p = a_p - (p_sum - width)
908 else:
909 d_p = d_p - (p_sum - width)
910
911 a_v = a if a > 0 else ''
912 d_v = d if d > 0 else ''
913
914 def cgen(l_type):
894 def cgen(l_type, a_v, d_v):
915 895 mapping = {'tr': 'top-right-rounded-corner-mid',
916 896 'tl': 'top-left-rounded-corner-mid',
917 897 'br': 'bottom-right-rounded-corner-mid',
918 898 'bl': 'bottom-left-rounded-corner-mid'}
919 899 map_getter = lambda x: mapping[x]
920 900
921 901 if l_type == 'a' and d_v:
922 902 #case when added and deleted are present
923 903 return ' '.join(map(map_getter, ['tl', 'bl']))
924 904
925 905 if l_type == 'a' and not d_v:
926 906 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
927 907
928 908 if l_type == 'd' and a_v:
929 909 return ' '.join(map(map_getter, ['tr', 'br']))
930 910
931 911 if l_type == 'd' and not a_v:
932 912 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
933 913
914 a, d = stats[0], stats[1]
915 width = 100
916
917 if a == 'b':
918 #binary mode
919 b_d = '<div class="bin%s %s" style="width:100%%">%s</div>' % (d, cgen('a', a_v='', d_v=0), 'bin')
920 b_a = '<div class="bin1" style="width:0%%">%s</div>' % ('bin')
921 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
922
923 t = stats[0] + stats[1]
924 unit = float(width) / (t or 1)
925
926 # needs > 9% of width to be visible or 0 to be hidden
927 a_p = max(9, unit * a) if a > 0 else 0
928 d_p = max(9, unit * d) if d > 0 else 0
929 p_sum = a_p + d_p
930
931 if p_sum > width:
932 #adjust the percentage to be == 100% since we adjusted to 9
933 if a_p > d_p:
934 a_p = a_p - (p_sum - width)
935 else:
936 d_p = d_p - (p_sum - width)
937
938 a_v = a if a > 0 else ''
939 d_v = d if d > 0 else ''
940
934 941 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
935 cgen('a'), a_p, a_v
942 cgen('a', a_v, d_v), a_p, a_v
936 943 )
937 944 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
938 cgen('d'), d_p, d_v
945 cgen('d', a_v, d_v), d_p, d_v
939 946 )
940 947 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
941 948
942 949
943 950 def urlify_text(text_):
944 951
945 952 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
946 953 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
947 954
948 955 def url_func(match_obj):
949 956 url_full = match_obj.groups()[0]
950 957 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
951 958
952 959 return literal(url_pat.sub(url_func, text_))
953 960
954 961
955 962 def urlify_changesets(text_, repository):
956 963 """
957 964 Extract revision ids from changeset and make link from them
958 965
959 966 :param text_:
960 967 :param repository:
961 968 """
962 969
963 970 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
964 971
965 972 def url_func(match_obj):
966 973 rev = match_obj.groups()[0]
967 974 pref = ''
968 975 if match_obj.group().startswith(' '):
969 976 pref = ' '
970 977 tmpl = (
971 978 '%(pref)s<a class="%(cls)s" href="%(url)s">'
972 979 '%(rev)s'
973 980 '</a>'
974 981 )
975 982 return tmpl % {
976 983 'pref': pref,
977 984 'cls': 'revision-link',
978 985 'url': url('changeset_home', repo_name=repository, revision=rev),
979 986 'rev': rev,
980 987 }
981 988
982 989 newtext = URL_PAT.sub(url_func, text_)
983 990
984 991 return newtext
985 992
986 993
987 994 def urlify_commit(text_, repository=None, link_=None):
988 995 """
989 996 Parses given text message and makes proper links.
990 997 issues are linked to given issue-server, and rest is a changeset link
991 998 if link_ is given, in other case it's a plain text
992 999
993 1000 :param text_:
994 1001 :param repository:
995 1002 :param link_: changeset link
996 1003 """
997 1004 import traceback
998 1005
999 1006 def escaper(string):
1000 1007 return string.replace('<', '&lt;').replace('>', '&gt;')
1001 1008
1002 1009 def linkify_others(t, l):
1003 1010 urls = re.compile(r'(\<a.*?\<\/a\>)',)
1004 1011 links = []
1005 1012 for e in urls.split(t):
1006 1013 if not urls.match(e):
1007 1014 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
1008 1015 else:
1009 1016 links.append(e)
1010 1017
1011 1018 return ''.join(links)
1012 1019
1013 1020 # urlify changesets - extrac revisions and make link out of them
1014 1021 newtext = urlify_changesets(escaper(text_), repository)
1015 1022
1016 1023 try:
1017 1024 conf = config['app_conf']
1018 1025
1019 1026 # allow multiple issue servers to be used
1020 1027 valid_indices = [
1021 1028 x.group(1)
1022 1029 for x in map(lambda x: re.match(r'issue_pat(.*)', x), conf.keys())
1023 1030 if x and 'issue_server_link%s' % x.group(1) in conf
1024 1031 and 'issue_prefix%s' % x.group(1) in conf
1025 1032 ]
1026 1033
1027 1034 log.debug('found issue server suffixes `%s` during valuation of: %s'
1028 1035 % (','.join(valid_indices), newtext))
1029 1036
1030 1037 for pattern_index in valid_indices:
1031 1038 ISSUE_PATTERN = conf.get('issue_pat%s' % pattern_index)
1032 1039 ISSUE_SERVER_LNK = conf.get('issue_server_link%s' % pattern_index)
1033 1040 ISSUE_PREFIX = conf.get('issue_prefix%s' % pattern_index)
1034 1041
1035 1042 log.debug('pattern suffix `%s` PAT:%s SERVER_LINK:%s PREFIX:%s'
1036 1043 % (pattern_index, ISSUE_PATTERN, ISSUE_SERVER_LNK,
1037 1044 ISSUE_PREFIX))
1038 1045
1039 1046 URL_PAT = re.compile(r'%s' % ISSUE_PATTERN)
1040 1047
1041 1048 def url_func(match_obj):
1042 1049 pref = ''
1043 1050 if match_obj.group().startswith(' '):
1044 1051 pref = ' '
1045 1052
1046 1053 issue_id = ''.join(match_obj.groups())
1047 1054 tmpl = (
1048 1055 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1049 1056 '%(issue-prefix)s%(id-repr)s'
1050 1057 '</a>'
1051 1058 )
1052 1059 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1053 1060 if repository:
1054 1061 url = url.replace('{repo}', repository)
1055 1062 repo_name = repository.split(URL_SEP)[-1]
1056 1063 url = url.replace('{repo_name}', repo_name)
1057 1064
1058 1065 return tmpl % {
1059 1066 'pref': pref,
1060 1067 'cls': 'issue-tracker-link',
1061 1068 'url': url,
1062 1069 'id-repr': issue_id,
1063 1070 'issue-prefix': ISSUE_PREFIX,
1064 1071 'serv': ISSUE_SERVER_LNK,
1065 1072 }
1066 1073 newtext = URL_PAT.sub(url_func, newtext)
1067 1074 log.debug('processed prefix:`%s` => %s' % (pattern_index, newtext))
1068 1075
1069 1076 # if we actually did something above
1070 1077 if link_:
1071 1078 # wrap not links into final link => link_
1072 1079 newtext = linkify_others(newtext, link_)
1073 1080 except:
1074 1081 log.error(traceback.format_exc())
1075 1082 pass
1076 1083
1077 1084 return literal(newtext)
1078 1085
1079 1086
1080 1087 def rst(source):
1081 1088 return literal('<div class="rst-block">%s</div>' %
1082 1089 MarkupRenderer.rst(source))
1083 1090
1084 1091
1085 1092 def rst_w_mentions(source):
1086 1093 """
1087 1094 Wrapped rst renderer with @mention highlighting
1088 1095
1089 1096 :param source:
1090 1097 """
1091 1098 return literal('<div class="rst-block">%s</div>' %
1092 1099 MarkupRenderer.rst_with_mentions(source))
1093 1100
1094 1101
1095 1102 def changeset_status(repo, revision):
1096 1103 return ChangesetStatusModel().get_status(repo, revision)
1097 1104
1098 1105
1099 1106 def changeset_status_lbl(changeset_status):
1100 1107 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1101 1108
1102 1109
1103 1110 def get_permission_name(key):
1104 1111 return dict(Permission.PERMS).get(key)
@@ -1,972 +1,972 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 vcs.backends.base
4 4 ~~~~~~~~~~~~~~~~~
5 5
6 6 Base for all available scm backends
7 7
8 8 :created_on: Apr 8, 2010
9 9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 10 """
11 11
12 12
13 13 from itertools import chain
14 14 from rhodecode.lib.vcs.utils import author_name, author_email
15 15 from rhodecode.lib.vcs.utils.lazy import LazyProperty
16 16 from rhodecode.lib.vcs.utils.helpers import get_dict_for_attrs
17 17 from rhodecode.lib.vcs.conf import settings
18 18
19 19 from rhodecode.lib.vcs.exceptions import ChangesetError, EmptyRepositoryError, \
20 20 NodeAlreadyAddedError, NodeAlreadyChangedError, NodeAlreadyExistsError, \
21 21 NodeAlreadyRemovedError, NodeDoesNotExistError, NodeNotChangedError, \
22 22 RepositoryError
23 23
24 24
25 25 class BaseRepository(object):
26 26 """
27 27 Base Repository for final backends
28 28
29 29 **Attributes**
30 30
31 31 ``DEFAULT_BRANCH_NAME``
32 32 name of default branch (i.e. "trunk" for svn, "master" for git etc.
33 33
34 34 ``scm``
35 35 alias of scm, i.e. *git* or *hg*
36 36
37 37 ``repo``
38 38 object from external api
39 39
40 40 ``revisions``
41 41 list of all available revisions' ids, in ascending order
42 42
43 43 ``changesets``
44 44 storage dict caching returned changesets
45 45
46 46 ``path``
47 47 absolute path to the repository
48 48
49 49 ``branches``
50 50 branches as list of changesets
51 51
52 52 ``tags``
53 53 tags as list of changesets
54 54 """
55 55 scm = None
56 56 DEFAULT_BRANCH_NAME = None
57 57 EMPTY_CHANGESET = '0' * 40
58 58
59 59 def __init__(self, repo_path, create=False, **kwargs):
60 60 """
61 61 Initializes repository. Raises RepositoryError if repository could
62 62 not be find at the given ``repo_path`` or directory at ``repo_path``
63 63 exists and ``create`` is set to True.
64 64
65 65 :param repo_path: local path of the repository
66 66 :param create=False: if set to True, would try to craete repository.
67 67 :param src_url=None: if set, should be proper url from which repository
68 68 would be cloned; requires ``create`` parameter to be set to True -
69 69 raises RepositoryError if src_url is set and create evaluates to
70 70 False
71 71 """
72 72 raise NotImplementedError
73 73
74 74 def __str__(self):
75 75 return '<%s at %s>' % (self.__class__.__name__, self.path)
76 76
77 77 def __repr__(self):
78 78 return self.__str__()
79 79
80 80 def __len__(self):
81 81 return self.count()
82 82
83 83 @LazyProperty
84 84 def alias(self):
85 85 for k, v in settings.BACKENDS.items():
86 86 if v.split('.')[-1] == str(self.__class__.__name__):
87 87 return k
88 88
89 89 @LazyProperty
90 90 def name(self):
91 91 raise NotImplementedError
92 92
93 93 @LazyProperty
94 94 def owner(self):
95 95 raise NotImplementedError
96 96
97 97 @LazyProperty
98 98 def description(self):
99 99 raise NotImplementedError
100 100
101 101 @LazyProperty
102 102 def size(self):
103 103 """
104 104 Returns combined size in bytes for all repository files
105 105 """
106 106
107 107 size = 0
108 108 try:
109 109 tip = self.get_changeset()
110 110 for topnode, dirs, files in tip.walk('/'):
111 111 for f in files:
112 112 size += tip.get_file_size(f.path)
113 113 for dir in dirs:
114 114 for f in files:
115 115 size += tip.get_file_size(f.path)
116 116
117 117 except RepositoryError, e:
118 118 pass
119 119 return size
120 120
121 121 def is_valid(self):
122 122 """
123 123 Validates repository.
124 124 """
125 125 raise NotImplementedError
126 126
127 127 def get_last_change(self):
128 128 self.get_changesets()
129 129
130 130 #==========================================================================
131 131 # CHANGESETS
132 132 #==========================================================================
133 133
134 134 def get_changeset(self, revision=None):
135 135 """
136 136 Returns instance of ``Changeset`` class. If ``revision`` is None, most
137 137 recent changeset is returned.
138 138
139 139 :raises ``EmptyRepositoryError``: if there are no revisions
140 140 """
141 141 raise NotImplementedError
142 142
143 143 def __iter__(self):
144 144 """
145 145 Allows Repository objects to be iterated.
146 146
147 147 *Requires* implementation of ``__getitem__`` method.
148 148 """
149 149 for revision in self.revisions:
150 150 yield self.get_changeset(revision)
151 151
152 152 def get_changesets(self, start=None, end=None, start_date=None,
153 153 end_date=None, branch_name=None, reverse=False):
154 154 """
155 155 Returns iterator of ``MercurialChangeset`` objects from start to end
156 156 not inclusive This should behave just like a list, ie. end is not
157 157 inclusive
158 158
159 159 :param start: None or str
160 160 :param end: None or str
161 161 :param start_date:
162 162 :param end_date:
163 163 :param branch_name:
164 164 :param reversed:
165 165 """
166 166 raise NotImplementedError
167 167
168 168 def __getslice__(self, i, j):
169 169 """
170 170 Returns a iterator of sliced repository
171 171 """
172 172 for rev in self.revisions[i:j]:
173 173 yield self.get_changeset(rev)
174 174
175 175 def __getitem__(self, key):
176 176 return self.get_changeset(key)
177 177
178 178 def count(self):
179 179 return len(self.revisions)
180 180
181 181 def tag(self, name, user, revision=None, message=None, date=None, **opts):
182 182 """
183 183 Creates and returns a tag for the given ``revision``.
184 184
185 185 :param name: name for new tag
186 186 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
187 187 :param revision: changeset id for which new tag would be created
188 188 :param message: message of the tag's commit
189 189 :param date: date of tag's commit
190 190
191 191 :raises TagAlreadyExistError: if tag with same name already exists
192 192 """
193 193 raise NotImplementedError
194 194
195 195 def remove_tag(self, name, user, message=None, date=None):
196 196 """
197 197 Removes tag with the given ``name``.
198 198
199 199 :param name: name of the tag to be removed
200 200 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
201 201 :param message: message of the tag's removal commit
202 202 :param date: date of tag's removal commit
203 203
204 204 :raises TagDoesNotExistError: if tag with given name does not exists
205 205 """
206 206 raise NotImplementedError
207 207
208 208 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
209 209 context=3):
210 210 """
211 211 Returns (git like) *diff*, as plain text. Shows changes introduced by
212 212 ``rev2`` since ``rev1``.
213 213
214 214 :param rev1: Entry point from which diff is shown. Can be
215 215 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
216 216 the changes since empty state of the repository until ``rev2``
217 217 :param rev2: Until which revision changes should be shown.
218 218 :param ignore_whitespace: If set to ``True``, would not show whitespace
219 219 changes. Defaults to ``False``.
220 220 :param context: How many lines before/after changed lines should be
221 221 shown. Defaults to ``3``.
222 222 """
223 223 raise NotImplementedError
224 224
225 225 # ========== #
226 226 # COMMIT API #
227 227 # ========== #
228 228
229 229 @LazyProperty
230 230 def in_memory_changeset(self):
231 231 """
232 232 Returns ``InMemoryChangeset`` object for this repository.
233 233 """
234 234 raise NotImplementedError
235 235
236 236 def add(self, filenode, **kwargs):
237 237 """
238 238 Commit api function that will add given ``FileNode`` into this
239 239 repository.
240 240
241 241 :raises ``NodeAlreadyExistsError``: if there is a file with same path
242 242 already in repository
243 243 :raises ``NodeAlreadyAddedError``: if given node is already marked as
244 244 *added*
245 245 """
246 246 raise NotImplementedError
247 247
248 248 def remove(self, filenode, **kwargs):
249 249 """
250 250 Commit api function that will remove given ``FileNode`` into this
251 251 repository.
252 252
253 253 :raises ``EmptyRepositoryError``: if there are no changesets yet
254 254 :raises ``NodeDoesNotExistError``: if there is no file with given path
255 255 """
256 256 raise NotImplementedError
257 257
258 258 def commit(self, message, **kwargs):
259 259 """
260 260 Persists current changes made on this repository and returns newly
261 261 created changeset.
262 262
263 263 :raises ``NothingChangedError``: if no changes has been made
264 264 """
265 265 raise NotImplementedError
266 266
267 267 def get_state(self):
268 268 """
269 269 Returns dictionary with ``added``, ``changed`` and ``removed`` lists
270 270 containing ``FileNode`` objects.
271 271 """
272 272 raise NotImplementedError
273 273
274 274 def get_config_value(self, section, name, config_file=None):
275 275 """
276 276 Returns configuration value for a given [``section``] and ``name``.
277 277
278 278 :param section: Section we want to retrieve value from
279 279 :param name: Name of configuration we want to retrieve
280 280 :param config_file: A path to file which should be used to retrieve
281 281 configuration from (might also be a list of file paths)
282 282 """
283 283 raise NotImplementedError
284 284
285 285 def get_user_name(self, config_file=None):
286 286 """
287 287 Returns user's name from global configuration file.
288 288
289 289 :param config_file: A path to file which should be used to retrieve
290 290 configuration from (might also be a list of file paths)
291 291 """
292 292 raise NotImplementedError
293 293
294 294 def get_user_email(self, config_file=None):
295 295 """
296 296 Returns user's email from global configuration file.
297 297
298 298 :param config_file: A path to file which should be used to retrieve
299 299 configuration from (might also be a list of file paths)
300 300 """
301 301 raise NotImplementedError
302 302
303 303 # =========== #
304 304 # WORKDIR API #
305 305 # =========== #
306 306
307 307 @LazyProperty
308 308 def workdir(self):
309 309 """
310 310 Returns ``Workdir`` instance for this repository.
311 311 """
312 312 raise NotImplementedError
313 313
314 314
315 315 class BaseChangeset(object):
316 316 """
317 317 Each backend should implement it's changeset representation.
318 318
319 319 **Attributes**
320 320
321 321 ``repository``
322 322 repository object within which changeset exists
323 323
324 324 ``id``
325 325 may be ``raw_id`` or i.e. for mercurial's tip just ``tip``
326 326
327 327 ``raw_id``
328 328 raw changeset representation (i.e. full 40 length sha for git
329 329 backend)
330 330
331 331 ``short_id``
332 332 shortened (if apply) version of ``raw_id``; it would be simple
333 333 shortcut for ``raw_id[:12]`` for git/mercurial backends or same
334 334 as ``raw_id`` for subversion
335 335
336 336 ``revision``
337 337 revision number as integer
338 338
339 339 ``files``
340 340 list of ``FileNode`` (``Node`` with NodeKind.FILE) objects
341 341
342 342 ``dirs``
343 343 list of ``DirNode`` (``Node`` with NodeKind.DIR) objects
344 344
345 345 ``nodes``
346 346 combined list of ``Node`` objects
347 347
348 348 ``author``
349 349 author of the changeset, as unicode
350 350
351 351 ``message``
352 352 message of the changeset, as unicode
353 353
354 354 ``parents``
355 355 list of parent changesets
356 356
357 357 ``last``
358 358 ``True`` if this is last changeset in repository, ``False``
359 359 otherwise; trying to access this attribute while there is no
360 360 changesets would raise ``EmptyRepositoryError``
361 361 """
362 362 def __str__(self):
363 363 return '<%s at %s:%s>' % (self.__class__.__name__, self.revision,
364 364 self.short_id)
365 365
366 366 def __repr__(self):
367 367 return self.__str__()
368 368
369 369 def __unicode__(self):
370 370 return u'%s:%s' % (self.revision, self.short_id)
371 371
372 372 def __eq__(self, other):
373 373 return self.raw_id == other.raw_id
374 374
375 375 def __json__(self):
376 376 return dict(
377 377 short_id=self.short_id,
378 378 raw_id=self.raw_id,
379 379 message=self.message,
380 380 date=self.date,
381 381 author=self.author,
382 382 )
383 383
384 384 @LazyProperty
385 385 def last(self):
386 386 if self.repository is None:
387 387 raise ChangesetError("Cannot check if it's most recent revision")
388 388 return self.raw_id == self.repository.revisions[-1]
389 389
390 390 @LazyProperty
391 391 def parents(self):
392 392 """
393 393 Returns list of parents changesets.
394 394 """
395 395 raise NotImplementedError
396 396
397 397 @LazyProperty
398 398 def children(self):
399 399 """
400 400 Returns list of children changesets.
401 401 """
402 402 raise NotImplementedError
403 403
404 404 @LazyProperty
405 405 def id(self):
406 406 """
407 407 Returns string identifying this changeset.
408 408 """
409 409 raise NotImplementedError
410 410
411 411 @LazyProperty
412 412 def raw_id(self):
413 413 """
414 414 Returns raw string identifying this changeset.
415 415 """
416 416 raise NotImplementedError
417 417
418 418 @LazyProperty
419 419 def short_id(self):
420 420 """
421 421 Returns shortened version of ``raw_id`` attribute, as string,
422 422 identifying this changeset, useful for web representation.
423 423 """
424 424 raise NotImplementedError
425 425
426 426 @LazyProperty
427 427 def revision(self):
428 428 """
429 429 Returns integer identifying this changeset.
430 430
431 431 """
432 432 raise NotImplementedError
433 433
434 434 @LazyProperty
435 435 def author(self):
436 436 """
437 437 Returns Author for given commit
438 438 """
439 439
440 440 raise NotImplementedError
441 441
442 442 @LazyProperty
443 443 def author_name(self):
444 444 """
445 445 Returns Author name for given commit
446 446 """
447 447
448 448 return author_name(self.author)
449 449
450 450 @LazyProperty
451 451 def author_email(self):
452 452 """
453 453 Returns Author email address for given commit
454 454 """
455 455
456 456 return author_email(self.author)
457 457
458 458 def get_file_mode(self, path):
459 459 """
460 460 Returns stat mode of the file at the given ``path``.
461 461 """
462 462 raise NotImplementedError
463 463
464 464 def get_file_content(self, path):
465 465 """
466 466 Returns content of the file at the given ``path``.
467 467 """
468 468 raise NotImplementedError
469 469
470 470 def get_file_size(self, path):
471 471 """
472 472 Returns size of the file at the given ``path``.
473 473 """
474 474 raise NotImplementedError
475 475
476 476 def get_file_changeset(self, path):
477 477 """
478 478 Returns last commit of the file at the given ``path``.
479 479 """
480 480 raise NotImplementedError
481 481
482 482 def get_file_history(self, path):
483 483 """
484 484 Returns history of file as reversed list of ``Changeset`` objects for
485 485 which file at given ``path`` has been modified.
486 486 """
487 487 raise NotImplementedError
488 488
489 489 def get_nodes(self, path):
490 490 """
491 491 Returns combined ``DirNode`` and ``FileNode`` objects list representing
492 492 state of changeset at the given ``path``.
493 493
494 494 :raises ``ChangesetError``: if node at the given ``path`` is not
495 495 instance of ``DirNode``
496 496 """
497 497 raise NotImplementedError
498 498
499 499 def get_node(self, path):
500 500 """
501 501 Returns ``Node`` object from the given ``path``.
502 502
503 503 :raises ``NodeDoesNotExistError``: if there is no node at the given
504 504 ``path``
505 505 """
506 506 raise NotImplementedError
507 507
508 508 def fill_archive(self, stream=None, kind='tgz', prefix=None):
509 509 """
510 510 Fills up given stream.
511 511
512 512 :param stream: file like object.
513 513 :param kind: one of following: ``zip``, ``tar``, ``tgz``
514 514 or ``tbz2``. Default: ``tgz``.
515 515 :param prefix: name of root directory in archive.
516 516 Default is repository name and changeset's raw_id joined with dash.
517 517
518 518 repo-tip.<kind>
519 519 """
520 520
521 521 raise NotImplementedError
522 522
523 523 def get_chunked_archive(self, **kwargs):
524 524 """
525 525 Returns iterable archive. Tiny wrapper around ``fill_archive`` method.
526 526
527 527 :param chunk_size: extra parameter which controls size of returned
528 528 chunks. Default:8k.
529 529 """
530 530
531 531 chunk_size = kwargs.pop('chunk_size', 8192)
532 532 stream = kwargs.get('stream')
533 533 self.fill_archive(**kwargs)
534 534 while True:
535 535 data = stream.read(chunk_size)
536 536 if not data:
537 537 break
538 538 yield data
539 539
540 540 @LazyProperty
541 541 def root(self):
542 542 """
543 543 Returns ``RootNode`` object for this changeset.
544 544 """
545 545 return self.get_node('')
546 546
547 547 def next(self, branch=None):
548 548 """
549 549 Returns next changeset from current, if branch is gives it will return
550 550 next changeset belonging to this branch
551 551
552 552 :param branch: show changesets within the given named branch
553 553 """
554 554 raise NotImplementedError
555 555
556 556 def prev(self, branch=None):
557 557 """
558 558 Returns previous changeset from current, if branch is gives it will
559 559 return previous changeset belonging to this branch
560 560
561 561 :param branch: show changesets within the given named branch
562 562 """
563 563 raise NotImplementedError
564 564
565 565 @LazyProperty
566 566 def added(self):
567 567 """
568 568 Returns list of added ``FileNode`` objects.
569 569 """
570 570 raise NotImplementedError
571 571
572 572 @LazyProperty
573 573 def changed(self):
574 574 """
575 575 Returns list of modified ``FileNode`` objects.
576 576 """
577 577 raise NotImplementedError
578 578
579 579 @LazyProperty
580 580 def removed(self):
581 581 """
582 582 Returns list of removed ``FileNode`` objects.
583 583 """
584 584 raise NotImplementedError
585 585
586 586 @LazyProperty
587 587 def size(self):
588 588 """
589 589 Returns total number of bytes from contents of all filenodes.
590 590 """
591 591 return sum((node.size for node in self.get_filenodes_generator()))
592 592
593 593 def walk(self, topurl=''):
594 594 """
595 595 Similar to os.walk method. Insted of filesystem it walks through
596 596 changeset starting at given ``topurl``. Returns generator of tuples
597 597 (topnode, dirnodes, filenodes).
598 598 """
599 599 topnode = self.get_node(topurl)
600 600 yield (topnode, topnode.dirs, topnode.files)
601 601 for dirnode in topnode.dirs:
602 602 for tup in self.walk(dirnode.path):
603 603 yield tup
604 604
605 605 def get_filenodes_generator(self):
606 606 """
607 607 Returns generator that yields *all* file nodes.
608 608 """
609 609 for topnode, dirs, files in self.walk():
610 610 for node in files:
611 611 yield node
612 612
613 613 def as_dict(self):
614 614 """
615 615 Returns dictionary with changeset's attributes and their values.
616 616 """
617 617 data = get_dict_for_attrs(self, ['id', 'raw_id', 'short_id',
618 618 'revision', 'date', 'message'])
619 619 data['author'] = {'name': self.author_name, 'email': self.author_email}
620 620 data['added'] = [node.path for node in self.added]
621 621 data['changed'] = [node.path for node in self.changed]
622 622 data['removed'] = [node.path for node in self.removed]
623 623 return data
624 624
625 625
626 626 class BaseWorkdir(object):
627 627 """
628 628 Working directory representation of single repository.
629 629
630 630 :attribute: repository: repository object of working directory
631 631 """
632 632
633 633 def __init__(self, repository):
634 634 self.repository = repository
635 635
636 636 def get_branch(self):
637 637 """
638 638 Returns name of current branch.
639 639 """
640 640 raise NotImplementedError
641 641
642 642 def get_changeset(self):
643 643 """
644 644 Returns current changeset.
645 645 """
646 646 raise NotImplementedError
647 647
648 648 def get_added(self):
649 649 """
650 650 Returns list of ``FileNode`` objects marked as *new* in working
651 651 directory.
652 652 """
653 653 raise NotImplementedError
654 654
655 655 def get_changed(self):
656 656 """
657 657 Returns list of ``FileNode`` objects *changed* in working directory.
658 658 """
659 659 raise NotImplementedError
660 660
661 661 def get_removed(self):
662 662 """
663 663 Returns list of ``RemovedFileNode`` objects marked as *removed* in
664 664 working directory.
665 665 """
666 666 raise NotImplementedError
667 667
668 668 def get_untracked(self):
669 669 """
670 670 Returns list of ``FileNode`` objects which are present within working
671 671 directory however are not tracked by repository.
672 672 """
673 673 raise NotImplementedError
674 674
675 675 def get_status(self):
676 676 """
677 677 Returns dict with ``added``, ``changed``, ``removed`` and ``untracked``
678 678 lists.
679 679 """
680 680 raise NotImplementedError
681 681
682 682 def commit(self, message, **kwargs):
683 683 """
684 684 Commits local (from working directory) changes and returns newly
685 685 created
686 686 ``Changeset``. Updates repository's ``revisions`` list.
687 687
688 688 :raises ``CommitError``: if any error occurs while committing
689 689 """
690 690 raise NotImplementedError
691 691
692 692 def update(self, revision=None):
693 693 """
694 694 Fetches content of the given revision and populates it within working
695 695 directory.
696 696 """
697 697 raise NotImplementedError
698 698
699 699 def checkout_branch(self, branch=None):
700 700 """
701 701 Checks out ``branch`` or the backend's default branch.
702 702
703 703 Raises ``BranchDoesNotExistError`` if the branch does not exist.
704 704 """
705 705 raise NotImplementedError
706 706
707 707
708 708 class BaseInMemoryChangeset(object):
709 709 """
710 710 Represents differences between repository's state (most recent head) and
711 711 changes made *in place*.
712 712
713 713 **Attributes**
714 714
715 715 ``repository``
716 716 repository object for this in-memory-changeset
717 717
718 718 ``added``
719 719 list of ``FileNode`` objects marked as *added*
720 720
721 721 ``changed``
722 722 list of ``FileNode`` objects marked as *changed*
723 723
724 724 ``removed``
725 725 list of ``FileNode`` or ``RemovedFileNode`` objects marked to be
726 726 *removed*
727 727
728 728 ``parents``
729 729 list of ``Changeset`` representing parents of in-memory changeset.
730 730 Should always be 2-element sequence.
731 731
732 732 """
733 733
734 734 def __init__(self, repository):
735 735 self.repository = repository
736 736 self.added = []
737 737 self.changed = []
738 738 self.removed = []
739 739 self.parents = []
740 740
741 741 def add(self, *filenodes):
742 742 """
743 743 Marks given ``FileNode`` objects as *to be committed*.
744 744
745 745 :raises ``NodeAlreadyExistsError``: if node with same path exists at
746 746 latest changeset
747 747 :raises ``NodeAlreadyAddedError``: if node with same path is already
748 748 marked as *added*
749 749 """
750 750 # Check if not already marked as *added* first
751 751 for node in filenodes:
752 752 if node.path in (n.path for n in self.added):
753 753 raise NodeAlreadyAddedError("Such FileNode %s is already "
754 754 "marked for addition" % node.path)
755 755 for node in filenodes:
756 756 self.added.append(node)
757 757
758 758 def change(self, *filenodes):
759 759 """
760 760 Marks given ``FileNode`` objects to be *changed* in next commit.
761 761
762 762 :raises ``EmptyRepositoryError``: if there are no changesets yet
763 763 :raises ``NodeAlreadyExistsError``: if node with same path is already
764 764 marked to be *changed*
765 765 :raises ``NodeAlreadyRemovedError``: if node with same path is already
766 766 marked to be *removed*
767 767 :raises ``NodeDoesNotExistError``: if node doesn't exist in latest
768 768 changeset
769 769 :raises ``NodeNotChangedError``: if node hasn't really be changed
770 770 """
771 771 for node in filenodes:
772 772 if node.path in (n.path for n in self.removed):
773 773 raise NodeAlreadyRemovedError("Node at %s is already marked "
774 774 "as removed" % node.path)
775 775 try:
776 776 self.repository.get_changeset()
777 777 except EmptyRepositoryError:
778 778 raise EmptyRepositoryError("Nothing to change - try to *add* new "
779 779 "nodes rather than changing them")
780 780 for node in filenodes:
781 781 if node.path in (n.path for n in self.changed):
782 782 raise NodeAlreadyChangedError("Node at '%s' is already "
783 783 "marked as changed" % node.path)
784 784 self.changed.append(node)
785 785
786 786 def remove(self, *filenodes):
787 787 """
788 788 Marks given ``FileNode`` (or ``RemovedFileNode``) objects to be
789 789 *removed* in next commit.
790 790
791 791 :raises ``NodeAlreadyRemovedError``: if node has been already marked to
792 792 be *removed*
793 793 :raises ``NodeAlreadyChangedError``: if node has been already marked to
794 794 be *changed*
795 795 """
796 796 for node in filenodes:
797 797 if node.path in (n.path for n in self.removed):
798 798 raise NodeAlreadyRemovedError("Node is already marked to "
799 799 "for removal at %s" % node.path)
800 800 if node.path in (n.path for n in self.changed):
801 801 raise NodeAlreadyChangedError("Node is already marked to "
802 802 "be changed at %s" % node.path)
803 803 # We only mark node as *removed* - real removal is done by
804 804 # commit method
805 805 self.removed.append(node)
806 806
807 807 def reset(self):
808 808 """
809 809 Resets this instance to initial state (cleans ``added``, ``changed``
810 810 and ``removed`` lists).
811 811 """
812 812 self.added = []
813 813 self.changed = []
814 814 self.removed = []
815 815 self.parents = []
816 816
817 817 def get_ipaths(self):
818 818 """
819 819 Returns generator of paths from nodes marked as added, changed or
820 820 removed.
821 821 """
822 822 for node in chain(self.added, self.changed, self.removed):
823 823 yield node.path
824 824
825 825 def get_paths(self):
826 826 """
827 827 Returns list of paths from nodes marked as added, changed or removed.
828 828 """
829 829 return list(self.get_ipaths())
830 830
831 831 def check_integrity(self, parents=None):
832 832 """
833 833 Checks in-memory changeset's integrity. Also, sets parents if not
834 834 already set.
835 835
836 836 :raises CommitError: if any error occurs (i.e.
837 837 ``NodeDoesNotExistError``).
838 838 """
839 839 if not self.parents:
840 840 parents = parents or []
841 841 if len(parents) == 0:
842 842 try:
843 843 parents = [self.repository.get_changeset(), None]
844 844 except EmptyRepositoryError:
845 845 parents = [None, None]
846 846 elif len(parents) == 1:
847 847 parents += [None]
848 848 self.parents = parents
849 849
850 850 # Local parents, only if not None
851 851 parents = [p for p in self.parents if p]
852 852
853 853 # Check nodes marked as added
854 854 for p in parents:
855 855 for node in self.added:
856 856 try:
857 857 p.get_node(node.path)
858 858 except NodeDoesNotExistError:
859 859 pass
860 860 else:
861 861 raise NodeAlreadyExistsError("Node at %s already exists "
862 862 "at %s" % (node.path, p))
863 863
864 864 # Check nodes marked as changed
865 865 missing = set(self.changed)
866 866 not_changed = set(self.changed)
867 867 if self.changed and not parents:
868 868 raise NodeDoesNotExistError(str(self.changed[0].path))
869 869 for p in parents:
870 870 for node in self.changed:
871 871 try:
872 872 old = p.get_node(node.path)
873 873 missing.remove(node)
874 874 if old.content != node.content:
875 875 not_changed.remove(node)
876 876 except NodeDoesNotExistError:
877 877 pass
878 878 if self.changed and missing:
879 879 raise NodeDoesNotExistError("Node at %s is missing "
880 880 "(parents: %s)" % (node.path, parents))
881 881
882 882 if self.changed and not_changed:
883 883 raise NodeNotChangedError("Node at %s wasn't actually changed "
884 884 "since parents' changesets: %s" % (not_changed.pop().path,
885 885 parents)
886 886 )
887 887
888 888 # Check nodes marked as removed
889 889 if self.removed and not parents:
890 890 raise NodeDoesNotExistError("Cannot remove node at %s as there "
891 891 "were no parents specified" % self.removed[0].path)
892 892 really_removed = set()
893 893 for p in parents:
894 894 for node in self.removed:
895 895 try:
896 896 p.get_node(node.path)
897 897 really_removed.add(node)
898 898 except ChangesetError:
899 899 pass
900 900 not_removed = set(self.removed) - really_removed
901 901 if not_removed:
902 902 raise NodeDoesNotExistError("Cannot remove node at %s from "
903 903 "following parents: %s" % (not_removed[0], parents))
904 904
905 905 def commit(self, message, author, parents=None, branch=None, date=None,
906 906 **kwargs):
907 907 """
908 908 Performs in-memory commit (doesn't check workdir in any way) and
909 909 returns newly created ``Changeset``. Updates repository's
910 910 ``revisions``.
911 911
912 912 .. note::
913 913 While overriding this method each backend's should call
914 914 ``self.check_integrity(parents)`` in the first place.
915 915
916 916 :param message: message of the commit
917 917 :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
918 918 :param parents: single parent or sequence of parents from which commit
919 919 would be derieved
920 920 :param date: ``datetime.datetime`` instance. Defaults to
921 921 ``datetime.datetime.now()``.
922 922 :param branch: branch name, as string. If none given, default backend's
923 923 branch would be used.
924 924
925 925 :raises ``CommitError``: if any error occurs while committing
926 926 """
927 927 raise NotImplementedError
928 928
929 929
930 930 class EmptyChangeset(BaseChangeset):
931 931 """
932 932 An dummy empty changeset. It's possible to pass hash when creating
933 933 an EmptyChangeset
934 934 """
935 935
936 936 def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
937 alias=None, message='', author='', date=''):
937 alias=None, revision=-1, message='', author='', date=''):
938 938 self._empty_cs = cs
939 self.revision = -1
939 self.revision = revision
940 940 self.message = message
941 941 self.author = author
942 942 self.date = date
943 943 self.repository = repo
944 944 self.requested_revision = requested_revision
945 945 self.alias = alias
946 946
947 947 @LazyProperty
948 948 def raw_id(self):
949 949 """
950 950 Returns raw string identifying this changeset, useful for web
951 951 representation.
952 952 """
953 953
954 954 return self._empty_cs
955 955
956 956 @LazyProperty
957 957 def branch(self):
958 958 from rhodecode.lib.vcs.backends import get_backend
959 959 return get_backend(self.alias).DEFAULT_BRANCH_NAME
960 960
961 961 @LazyProperty
962 962 def short_id(self):
963 963 return self.raw_id[:12]
964 964
965 965 def get_file_changeset(self, path):
966 966 return self
967 967
968 968 def get_file_content(self, path):
969 969 return u''
970 970
971 971 def get_file_size(self, path):
972 972 return 0
@@ -1,658 +1,659 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 vcs.backends.git
4 4 ~~~~~~~~~~~~~~~~
5 5
6 6 Git backend implementation.
7 7
8 8 :created_on: Apr 8, 2010
9 9 :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
10 10 """
11 11
12 12 import os
13 13 import re
14 14 import time
15 15 import posixpath
16 16 import logging
17 17 import traceback
18 18 import urllib
19 19 import urllib2
20 20 from dulwich.repo import Repo, NotGitRepository
21 21 from dulwich.objects import Tag
22 22 from string import Template
23 23 from subprocess import Popen, PIPE
24 24 from rhodecode.lib.vcs.backends.base import BaseRepository
25 25 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
26 26 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
27 27 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
28 28 from rhodecode.lib.vcs.exceptions import RepositoryError
29 29 from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
30 30 from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
31 31 from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
32 32 from rhodecode.lib.vcs.utils.lazy import LazyProperty
33 33 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
34 34 from rhodecode.lib.vcs.utils.paths import abspath
35 35 from rhodecode.lib.vcs.utils.paths import get_user_home
36 36 from .workdir import GitWorkdir
37 37 from .changeset import GitChangeset
38 38 from .inmemory import GitInMemoryChangeset
39 39 from .config import ConfigFile
40 40 from rhodecode.lib import subprocessio
41 41
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 class GitRepository(BaseRepository):
47 47 """
48 48 Git repository backend.
49 49 """
50 50 DEFAULT_BRANCH_NAME = 'master'
51 51 scm = 'git'
52 52
53 53 def __init__(self, repo_path, create=False, src_url=None,
54 54 update_after_clone=False, bare=False):
55 55
56 56 self.path = abspath(repo_path)
57 57 self._repo = self._get_repo(create, src_url, update_after_clone, bare)
58 58 #temporary set that to now at later we will move it to constructor
59 59 baseui = None
60 60 if baseui is None:
61 61 from mercurial.ui import ui
62 62 baseui = ui()
63 63 # patch the instance of GitRepo with an "FAKE" ui object to add
64 64 # compatibility layer with Mercurial
65 65 setattr(self._repo, 'ui', baseui)
66 66
67 67 try:
68 68 self.head = self._repo.head()
69 69 except KeyError:
70 70 self.head = None
71 71
72 72 self._config_files = [
73 73 bare and abspath(self.path, 'config') or abspath(self.path, '.git',
74 74 'config'),
75 75 abspath(get_user_home(), '.gitconfig'),
76 76 ]
77 77 self.bare = self._repo.bare
78 78
79 79 @LazyProperty
80 80 def revisions(self):
81 81 """
82 82 Returns list of revisions' ids, in ascending order. Being lazy
83 83 attribute allows external tools to inject shas from cache.
84 84 """
85 85 return self._get_all_revisions()
86 86
87 87 def run_git_command(self, cmd):
88 88 """
89 89 Runs given ``cmd`` as git command and returns tuple
90 90 (returncode, stdout, stderr).
91 91
92 92 .. note::
93 93 This method exists only until log/blame functionality is implemented
94 94 at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
95 95 os command's output is road to hell...
96 96
97 97 :param cmd: git command to be executed
98 98 """
99 99
100 100 _copts = ['-c', 'core.quotepath=false', ]
101 101 _str_cmd = False
102 102 if isinstance(cmd, basestring):
103 103 cmd = [cmd]
104 104 _str_cmd = True
105 105
106 106 gitenv = os.environ
107 107 # need to clean fix GIT_DIR !
108 108 if 'GIT_DIR' in gitenv:
109 109 del gitenv['GIT_DIR']
110 110 gitenv['GIT_CONFIG_NOGLOBAL'] = '1'
111 111
112 112 cmd = ['git'] + _copts + cmd
113 113 if _str_cmd:
114 114 cmd = ' '.join(cmd)
115 115 try:
116 116 opts = dict(
117 117 env=gitenv,
118 118 shell=False,
119 119 )
120 120 if os.path.isdir(self.path):
121 121 opts['cwd'] = self.path
122 122 p = subprocessio.SubprocessIOChunker(cmd, **opts)
123 123 except (EnvironmentError, OSError), err:
124 124 log.error(traceback.format_exc())
125 125 raise RepositoryError("Couldn't run git command (%s).\n"
126 126 "Original error was:%s" % (cmd, err))
127 127
128 128 return ''.join(p.output), ''.join(p.error)
129 129
130 130 @classmethod
131 131 def _check_url(cls, url):
132 132 """
133 133 Functon will check given url and try to verify if it's a valid
134 134 link. Sometimes it may happened that mercurial will issue basic
135 135 auth request that can cause whole API to hang when used from python
136 136 or other external calls.
137 137
138 138 On failures it'll raise urllib2.HTTPError
139 139 """
140 140 from mercurial.util import url as Url
141 141
142 142 # those authnadlers are patched for python 2.6.5 bug an
143 143 # infinit looping when given invalid resources
144 144 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
145 145
146 146 # check first if it's not an local url
147 147 if os.path.isdir(url) or url.startswith('file:'):
148 148 return True
149 149
150 150 if('+' in url[:url.find('://')]):
151 151 url = url[url.find('+') + 1:]
152 152
153 153 handlers = []
154 154 test_uri, authinfo = Url(url).authinfo()
155 155 if not test_uri.endswith('info/refs'):
156 156 test_uri = test_uri.rstrip('/') + '/info/refs'
157 157 if authinfo:
158 158 #create a password manager
159 159 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
160 160 passmgr.add_password(*authinfo)
161 161
162 162 handlers.extend((httpbasicauthhandler(passmgr),
163 163 httpdigestauthhandler(passmgr)))
164 164
165 165 o = urllib2.build_opener(*handlers)
166 166 o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git
167 167
168 168 q = {"service": 'git-upload-pack'}
169 169 qs = '?%s' % urllib.urlencode(q)
170 170 cu = "%s%s" % (test_uri, qs)
171 171 req = urllib2.Request(cu, None, {})
172 172
173 173 try:
174 174 resp = o.open(req)
175 175 return resp.code == 200
176 176 except Exception, e:
177 177 # means it cannot be cloned
178 178 raise urllib2.URLError("[%s] %s" % (url, e))
179 179
180 180 def _get_repo(self, create, src_url=None, update_after_clone=False,
181 181 bare=False):
182 182 if create and os.path.exists(self.path):
183 183 raise RepositoryError("Location already exist")
184 184 if src_url and not create:
185 185 raise RepositoryError("Create should be set to True if src_url is "
186 186 "given (clone operation creates repository)")
187 187 try:
188 188 if create and src_url:
189 189 GitRepository._check_url(src_url)
190 190 self.clone(src_url, update_after_clone, bare)
191 191 return Repo(self.path)
192 192 elif create:
193 193 os.mkdir(self.path)
194 194 if bare:
195 195 return Repo.init_bare(self.path)
196 196 else:
197 197 return Repo.init(self.path)
198 198 else:
199 199 return Repo(self.path)
200 200 except (NotGitRepository, OSError), err:
201 201 raise RepositoryError(err)
202 202
203 203 def _get_all_revisions(self):
204 204 # we must check if this repo is not empty, since later command
205 205 # fails if it is. And it's cheaper to ask than throw the subprocess
206 206 # errors
207 207 try:
208 208 self._repo.head()
209 209 except KeyError:
210 210 return []
211 211 cmd = 'rev-list --all --reverse --date-order'
212 212 try:
213 213 so, se = self.run_git_command(cmd)
214 214 except RepositoryError:
215 215 # Can be raised for empty repositories
216 216 return []
217 217 return so.splitlines()
218 218
219 219 def _get_all_revisions2(self):
220 220 #alternate implementation using dulwich
221 221 includes = [x[1][0] for x in self._parsed_refs.iteritems()
222 222 if x[1][1] != 'T']
223 223 return [c.commit.id for c in self._repo.get_walker(include=includes)]
224 224
225 225 def _get_revision(self, revision):
226 226 """
227 227 For git backend we always return integer here. This way we ensure
228 228 that changset's revision attribute would become integer.
229 229 """
230 230 pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
231 231 is_bstr = lambda o: isinstance(o, (str, unicode))
232 232 is_null = lambda o: len(o) == revision.count('0')
233 233
234 234 if len(self.revisions) == 0:
235 235 raise EmptyRepositoryError("There are no changesets yet")
236 236
237 237 if revision in (None, '', 'tip', 'HEAD', 'head', -1):
238 238 revision = self.revisions[-1]
239 239
240 240 if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
241 241 or isinstance(revision, int) or is_null(revision)):
242 242 try:
243 243 revision = self.revisions[int(revision)]
244 244 except:
245 245 raise ChangesetDoesNotExistError("Revision %r does not exist "
246 246 "for this repository %s" % (revision, self))
247 247
248 248 elif is_bstr(revision):
249 249 # get by branch/tag name
250 250 _ref_revision = self._parsed_refs.get(revision)
251 251 _tags_shas = self.tags.values()
252 252 if _ref_revision: # and _ref_revision[1] in ['H', 'RH', 'T']:
253 253 return _ref_revision[0]
254 254
255 255 # maybe it's a tag ? we don't have them in self.revisions
256 256 elif revision in _tags_shas:
257 257 return _tags_shas[_tags_shas.index(revision)]
258 258
259 259 elif not pattern.match(revision) or revision not in self.revisions:
260 260 raise ChangesetDoesNotExistError("Revision %r does not exist "
261 261 "for this repository %s" % (revision, self))
262 262
263 263 # Ensure we return full id
264 264 if not pattern.match(str(revision)):
265 265 raise ChangesetDoesNotExistError("Given revision %r not recognized"
266 266 % revision)
267 267 return revision
268 268
269 269 def _get_archives(self, archive_name='tip'):
270 270
271 271 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
272 272 yield {"type": i[0], "extension": i[1], "node": archive_name}
273 273
274 274 def _get_url(self, url):
275 275 """
276 276 Returns normalized url. If schema is not given, would fall to
277 277 filesystem (``file:///``) schema.
278 278 """
279 279 url = str(url)
280 280 if url != 'default' and not '://' in url:
281 281 url = ':///'.join(('file', url))
282 282 return url
283 283
284 284 @LazyProperty
285 285 def name(self):
286 286 return os.path.basename(self.path)
287 287
288 288 @LazyProperty
289 289 def last_change(self):
290 290 """
291 291 Returns last change made on this repository as datetime object
292 292 """
293 293 return date_fromtimestamp(self._get_mtime(), makedate()[1])
294 294
295 295 def _get_mtime(self):
296 296 try:
297 297 return time.mktime(self.get_changeset().date.timetuple())
298 298 except RepositoryError:
299 299 idx_loc = '' if self.bare else '.git'
300 300 # fallback to filesystem
301 301 in_path = os.path.join(self.path, idx_loc, "index")
302 302 he_path = os.path.join(self.path, idx_loc, "HEAD")
303 303 if os.path.exists(in_path):
304 304 return os.stat(in_path).st_mtime
305 305 else:
306 306 return os.stat(he_path).st_mtime
307 307
308 308 @LazyProperty
309 309 def description(self):
310 310 idx_loc = '' if self.bare else '.git'
311 311 undefined_description = u'unknown'
312 312 description_path = os.path.join(self.path, idx_loc, 'description')
313 313 if os.path.isfile(description_path):
314 314 return safe_unicode(open(description_path).read())
315 315 else:
316 316 return undefined_description
317 317
318 318 @LazyProperty
319 319 def contact(self):
320 320 undefined_contact = u'Unknown'
321 321 return undefined_contact
322 322
323 323 @property
324 324 def branches(self):
325 325 if not self.revisions:
326 326 return {}
327 327 sortkey = lambda ctx: ctx[0]
328 328 _branches = [(x[0], x[1][0])
329 329 for x in self._parsed_refs.iteritems() if x[1][1] == 'H']
330 330 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
331 331
332 332 @LazyProperty
333 333 def tags(self):
334 334 return self._get_tags()
335 335
336 336 def _get_tags(self):
337 337 if not self.revisions:
338 338 return {}
339 339
340 340 sortkey = lambda ctx: ctx[0]
341 341 _tags = [(x[0], x[1][0])
342 342 for x in self._parsed_refs.iteritems() if x[1][1] == 'T']
343 343 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
344 344
345 345 def tag(self, name, user, revision=None, message=None, date=None,
346 346 **kwargs):
347 347 """
348 348 Creates and returns a tag for the given ``revision``.
349 349
350 350 :param name: name for new tag
351 351 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
352 352 :param revision: changeset id for which new tag would be created
353 353 :param message: message of the tag's commit
354 354 :param date: date of tag's commit
355 355
356 356 :raises TagAlreadyExistError: if tag with same name already exists
357 357 """
358 358 if name in self.tags:
359 359 raise TagAlreadyExistError("Tag %s already exists" % name)
360 360 changeset = self.get_changeset(revision)
361 361 message = message or "Added tag %s for commit %s" % (name,
362 362 changeset.raw_id)
363 363 self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
364 364
365 365 self._parsed_refs = self._get_parsed_refs()
366 366 self.tags = self._get_tags()
367 367 return changeset
368 368
369 369 def remove_tag(self, name, user, message=None, date=None):
370 370 """
371 371 Removes tag with the given ``name``.
372 372
373 373 :param name: name of the tag to be removed
374 374 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
375 375 :param message: message of the tag's removal commit
376 376 :param date: date of tag's removal commit
377 377
378 378 :raises TagDoesNotExistError: if tag with given name does not exists
379 379 """
380 380 if name not in self.tags:
381 381 raise TagDoesNotExistError("Tag %s does not exist" % name)
382 382 tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
383 383 try:
384 384 os.remove(tagpath)
385 385 self._parsed_refs = self._get_parsed_refs()
386 386 self.tags = self._get_tags()
387 387 except OSError, e:
388 388 raise RepositoryError(e.strerror)
389 389
390 390 @LazyProperty
391 391 def _parsed_refs(self):
392 392 return self._get_parsed_refs()
393 393
394 394 def _get_parsed_refs(self):
395 395 refs = self._repo.get_refs()
396 396 keys = [('refs/heads/', 'H'),
397 397 ('refs/remotes/origin/', 'RH'),
398 398 ('refs/tags/', 'T')]
399 399 _refs = {}
400 400 for ref, sha in refs.iteritems():
401 401 for k, type_ in keys:
402 402 if ref.startswith(k):
403 403 _key = ref[len(k):]
404 404 if type_ == 'T':
405 405 obj = self._repo.get_object(sha)
406 406 if isinstance(obj, Tag):
407 407 sha = self._repo.get_object(sha).object[1]
408 408 _refs[_key] = [sha, type_]
409 409 break
410 410 return _refs
411 411
412 412 def _heads(self, reverse=False):
413 413 refs = self._repo.get_refs()
414 414 heads = {}
415 415
416 416 for key, val in refs.items():
417 417 for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
418 418 if key.startswith(ref_key):
419 419 n = key[len(ref_key):]
420 420 if n not in ['HEAD']:
421 421 heads[n] = val
422 422
423 423 return heads if reverse else dict((y, x) for x, y in heads.iteritems())
424 424
425 425 def get_changeset(self, revision=None):
426 426 """
427 427 Returns ``GitChangeset`` object representing commit from git repository
428 428 at the given revision or head (most recent commit) if None given.
429 429 """
430 430 if isinstance(revision, GitChangeset):
431 431 return revision
432 432 revision = self._get_revision(revision)
433 433 changeset = GitChangeset(repository=self, revision=revision)
434 434 return changeset
435 435
436 436 def get_changesets(self, start=None, end=None, start_date=None,
437 437 end_date=None, branch_name=None, reverse=False):
438 438 """
439 439 Returns iterator of ``GitChangeset`` objects from start to end (both
440 440 are inclusive), in ascending date order (unless ``reverse`` is set).
441 441
442 442 :param start: changeset ID, as str; first returned changeset
443 443 :param end: changeset ID, as str; last returned changeset
444 444 :param start_date: if specified, changesets with commit date less than
445 445 ``start_date`` would be filtered out from returned set
446 446 :param end_date: if specified, changesets with commit date greater than
447 447 ``end_date`` would be filtered out from returned set
448 448 :param branch_name: if specified, changesets not reachable from given
449 449 branch would be filtered out from returned set
450 450 :param reverse: if ``True``, returned generator would be reversed
451 451 (meaning that returned changesets would have descending date order)
452 452
453 453 :raise BranchDoesNotExistError: If given ``branch_name`` does not
454 454 exist.
455 455 :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
456 456 ``end`` could not be found.
457 457
458 458 """
459 459 if branch_name and branch_name not in self.branches:
460 460 raise BranchDoesNotExistError("Branch '%s' not found" \
461 461 % branch_name)
462 462 # %H at format means (full) commit hash, initial hashes are retrieved
463 463 # in ascending date order
464 464 cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
465 465 cmd_params = {}
466 466 if start_date:
467 467 cmd_template += ' --since "$since"'
468 468 cmd_params['since'] = start_date.strftime('%m/%d/%y %H:%M:%S')
469 469 if end_date:
470 470 cmd_template += ' --until "$until"'
471 471 cmd_params['until'] = end_date.strftime('%m/%d/%y %H:%M:%S')
472 472 if branch_name:
473 473 cmd_template += ' $branch_name'
474 474 cmd_params['branch_name'] = branch_name
475 475 else:
476 476 cmd_template += ' --all'
477 477
478 478 cmd = Template(cmd_template).safe_substitute(**cmd_params)
479 479 revs = self.run_git_command(cmd)[0].splitlines()
480 480 start_pos = 0
481 481 end_pos = len(revs)
482 482 if start:
483 483 _start = self._get_revision(start)
484 484 try:
485 485 start_pos = revs.index(_start)
486 486 except ValueError:
487 487 pass
488 488
489 489 if end is not None:
490 490 _end = self._get_revision(end)
491 491 try:
492 492 end_pos = revs.index(_end)
493 493 except ValueError:
494 494 pass
495 495
496 496 if None not in [start, end] and start_pos > end_pos:
497 497 raise RepositoryError('start cannot be after end')
498 498
499 499 if end_pos is not None:
500 500 end_pos += 1
501 501
502 502 revs = revs[start_pos:end_pos]
503 503 if reverse:
504 504 revs = reversed(revs)
505 505 for rev in revs:
506 506 yield self.get_changeset(rev)
507 507
508 508 def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
509 509 context=3):
510 510 """
511 511 Returns (git like) *diff*, as plain text. Shows changes introduced by
512 512 ``rev2`` since ``rev1``.
513 513
514 514 :param rev1: Entry point from which diff is shown. Can be
515 515 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
516 516 the changes since empty state of the repository until ``rev2``
517 517 :param rev2: Until which revision changes should be shown.
518 518 :param ignore_whitespace: If set to ``True``, would not show whitespace
519 519 changes. Defaults to ``False``.
520 520 :param context: How many lines before/after changed lines should be
521 521 shown. Defaults to ``3``.
522 522 """
523 flags = ['-U%s' % context]
523 flags = ['-U%s' % context, '--full-index', '--binary', '-p', '-M', '--abbrev=40']
524 524 if ignore_whitespace:
525 525 flags.append('-w')
526 526
527 527 if hasattr(rev1, 'raw_id'):
528 528 rev1 = getattr(rev1, 'raw_id')
529 529
530 530 if hasattr(rev2, 'raw_id'):
531 531 rev2 = getattr(rev2, 'raw_id')
532 532
533 533 if rev1 == self.EMPTY_CHANGESET:
534 534 rev2 = self.get_changeset(rev2).raw_id
535 535 cmd = ' '.join(['show'] + flags + [rev2])
536 536 else:
537 537 rev1 = self.get_changeset(rev1).raw_id
538 538 rev2 = self.get_changeset(rev2).raw_id
539 539 cmd = ' '.join(['diff'] + flags + [rev1, rev2])
540 540
541 541 if path:
542 542 cmd += ' -- "%s"' % path
543
543 544 stdout, stderr = self.run_git_command(cmd)
544 545 # If we used 'show' command, strip first few lines (until actual diff
545 546 # starts)
546 547 if rev1 == self.EMPTY_CHANGESET:
547 548 lines = stdout.splitlines()
548 549 x = 0
549 550 for line in lines:
550 551 if line.startswith('diff'):
551 552 break
552 553 x += 1
553 554 # Append new line just like 'diff' command do
554 555 stdout = '\n'.join(lines[x:]) + '\n'
555 556 return stdout
556 557
557 558 @LazyProperty
558 559 def in_memory_changeset(self):
559 560 """
560 561 Returns ``GitInMemoryChangeset`` object for this repository.
561 562 """
562 563 return GitInMemoryChangeset(self)
563 564
564 565 def clone(self, url, update_after_clone=True, bare=False):
565 566 """
566 567 Tries to clone changes from external location.
567 568
568 569 :param update_after_clone: If set to ``False``, git won't checkout
569 570 working directory
570 571 :param bare: If set to ``True``, repository would be cloned into
571 572 *bare* git repository (no working directory at all).
572 573 """
573 574 url = self._get_url(url)
574 575 cmd = ['clone']
575 576 if bare:
576 577 cmd.append('--bare')
577 578 elif not update_after_clone:
578 579 cmd.append('--no-checkout')
579 580 cmd += ['--', '"%s"' % url, '"%s"' % self.path]
580 581 cmd = ' '.join(cmd)
581 582 # If error occurs run_git_command raises RepositoryError already
582 583 self.run_git_command(cmd)
583 584
584 585 def pull(self, url):
585 586 """
586 587 Tries to pull changes from external location.
587 588 """
588 589 url = self._get_url(url)
589 590 cmd = ['pull']
590 591 cmd.append("--ff-only")
591 592 cmd.append(url)
592 593 cmd = ' '.join(cmd)
593 594 # If error occurs run_git_command raises RepositoryError already
594 595 self.run_git_command(cmd)
595 596
596 597 def fetch(self, url):
597 598 """
598 599 Tries to pull changes from external location.
599 600 """
600 601 url = self._get_url(url)
601 602 cmd = ['fetch']
602 603 cmd.append(url)
603 604 cmd = ' '.join(cmd)
604 605 # If error occurs run_git_command raises RepositoryError already
605 606 self.run_git_command(cmd)
606 607
607 608 @LazyProperty
608 609 def workdir(self):
609 610 """
610 611 Returns ``Workdir`` instance for this repository.
611 612 """
612 613 return GitWorkdir(self)
613 614
614 615 def get_config_value(self, section, name, config_file=None):
615 616 """
616 617 Returns configuration value for a given [``section``] and ``name``.
617 618
618 619 :param section: Section we want to retrieve value from
619 620 :param name: Name of configuration we want to retrieve
620 621 :param config_file: A path to file which should be used to retrieve
621 622 configuration from (might also be a list of file paths)
622 623 """
623 624 if config_file is None:
624 625 config_file = []
625 626 elif isinstance(config_file, basestring):
626 627 config_file = [config_file]
627 628
628 629 def gen_configs():
629 630 for path in config_file + self._config_files:
630 631 try:
631 632 yield ConfigFile.from_path(path)
632 633 except (IOError, OSError, ValueError):
633 634 continue
634 635
635 636 for config in gen_configs():
636 637 try:
637 638 return config.get(section, name)
638 639 except KeyError:
639 640 continue
640 641 return None
641 642
642 643 def get_user_name(self, config_file=None):
643 644 """
644 645 Returns user's name from global configuration file.
645 646
646 647 :param config_file: A path to file which should be used to retrieve
647 648 configuration from (might also be a list of file paths)
648 649 """
649 650 return self.get_config_value('user', 'name', config_file)
650 651
651 652 def get_user_email(self, config_file=None):
652 653 """
653 654 Returns user's email from global configuration file.
654 655
655 656 :param config_file: A path to file which should be used to retrieve
656 657 configuration from (might also be a list of file paths)
657 658 """
658 659 return self.get_config_value('user', 'email', config_file)
@@ -1,533 +1,536 b''
1 1 import os
2 2 import time
3 3 import datetime
4 4 import urllib
5 5 import urllib2
6 6
7 7 from rhodecode.lib.vcs.backends.base import BaseRepository
8 8 from .workdir import MercurialWorkdir
9 9 from .changeset import MercurialChangeset
10 10 from .inmemory import MercurialInMemoryChangeset
11 11
12 12 from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError, \
13 13 ChangesetDoesNotExistError, EmptyRepositoryError, RepositoryError, \
14 14 VCSError, TagAlreadyExistError, TagDoesNotExistError
15 15 from rhodecode.lib.vcs.utils import author_email, author_name, date_fromtimestamp, \
16 16 makedate, safe_unicode
17 17 from rhodecode.lib.vcs.utils.lazy import LazyProperty
18 18 from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
19 19 from rhodecode.lib.vcs.utils.paths import abspath
20 20
21 21 from rhodecode.lib.vcs.utils.hgcompat import ui, nullid, match, patch, diffopts, clone, \
22 22 get_contact, pull, localrepository, RepoLookupError, Abort, RepoError, hex
23 23
24 24
25 25 class MercurialRepository(BaseRepository):
26 26 """
27 27 Mercurial repository backend
28 28 """
29 29 DEFAULT_BRANCH_NAME = 'default'
30 30 scm = 'hg'
31 31
32 32 def __init__(self, repo_path, create=False, baseui=None, src_url=None,
33 33 update_after_clone=False):
34 34 """
35 35 Raises RepositoryError if repository could not be find at the given
36 36 ``repo_path``.
37 37
38 38 :param repo_path: local path of the repository
39 39 :param create=False: if set to True, would try to create repository if
40 40 it does not exist rather than raising exception
41 41 :param baseui=None: user data
42 42 :param src_url=None: would try to clone repository from given location
43 43 :param update_after_clone=False: sets update of working copy after
44 44 making a clone
45 45 """
46 46
47 47 if not isinstance(repo_path, str):
48 48 raise VCSError('Mercurial backend requires repository path to '
49 49 'be instance of <str> got %s instead' %
50 50 type(repo_path))
51 51
52 52 self.path = abspath(repo_path)
53 53 self.baseui = baseui or ui.ui()
54 54 # We've set path and ui, now we can set _repo itself
55 55 self._repo = self._get_repo(create, src_url, update_after_clone)
56 56
57 57 @property
58 58 def _empty(self):
59 59 """
60 60 Checks if repository is empty without any changesets
61 61 """
62 62 # TODO: Following raises errors when using InMemoryChangeset...
63 63 # return len(self._repo.changelog) == 0
64 64 return len(self.revisions) == 0
65 65
66 66 @LazyProperty
67 67 def revisions(self):
68 68 """
69 69 Returns list of revisions' ids, in ascending order. Being lazy
70 70 attribute allows external tools to inject shas from cache.
71 71 """
72 72 return self._get_all_revisions()
73 73
74 74 @LazyProperty
75 75 def name(self):
76 76 return os.path.basename(self.path)
77 77
78 78 @LazyProperty
79 79 def branches(self):
80 80 return self._get_branches()
81 81
82 82 def _get_branches(self, closed=False):
83 83 """
84 84 Get's branches for this repository
85 85 Returns only not closed branches by default
86 86
87 87 :param closed: return also closed branches for mercurial
88 88 """
89 89
90 90 if self._empty:
91 91 return {}
92 92
93 93 def _branchtags(localrepo):
94 94 """
95 95 Patched version of mercurial branchtags to not return the closed
96 96 branches
97 97
98 98 :param localrepo: locarepository instance
99 99 """
100 100
101 101 bt = {}
102 102 bt_closed = {}
103 103 for bn, heads in localrepo.branchmap().iteritems():
104 104 tip = heads[-1]
105 105 if 'close' in localrepo.changelog.read(tip)[5]:
106 106 bt_closed[bn] = tip
107 107 else:
108 108 bt[bn] = tip
109 109
110 110 if closed:
111 111 bt.update(bt_closed)
112 112 return bt
113 113
114 114 sortkey = lambda ctx: ctx[0] # sort by name
115 115 _branches = [(safe_unicode(n), hex(h),) for n, h in
116 116 _branchtags(self._repo).items()]
117 117
118 118 return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
119 119
120 120 @LazyProperty
121 121 def tags(self):
122 122 """
123 123 Get's tags for this repository
124 124 """
125 125 return self._get_tags()
126 126
127 127 def _get_tags(self):
128 128 if self._empty:
129 129 return {}
130 130
131 131 sortkey = lambda ctx: ctx[0] # sort by name
132 132 _tags = [(safe_unicode(n), hex(h),) for n, h in
133 133 self._repo.tags().items()]
134 134
135 135 return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
136 136
137 137 def tag(self, name, user, revision=None, message=None, date=None,
138 138 **kwargs):
139 139 """
140 140 Creates and returns a tag for the given ``revision``.
141 141
142 142 :param name: name for new tag
143 143 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
144 144 :param revision: changeset id for which new tag would be created
145 145 :param message: message of the tag's commit
146 146 :param date: date of tag's commit
147 147
148 148 :raises TagAlreadyExistError: if tag with same name already exists
149 149 """
150 150 if name in self.tags:
151 151 raise TagAlreadyExistError("Tag %s already exists" % name)
152 152 changeset = self.get_changeset(revision)
153 153 local = kwargs.setdefault('local', False)
154 154
155 155 if message is None:
156 156 message = "Added tag %s for changeset %s" % (name,
157 157 changeset.short_id)
158 158
159 159 if date is None:
160 160 date = datetime.datetime.now().ctime()
161 161
162 162 try:
163 163 self._repo.tag(name, changeset._ctx.node(), message, local, user,
164 164 date)
165 165 except Abort, e:
166 166 raise RepositoryError(e.message)
167 167
168 168 # Reinitialize tags
169 169 self.tags = self._get_tags()
170 170 tag_id = self.tags[name]
171 171
172 172 return self.get_changeset(revision=tag_id)
173 173
174 174 def remove_tag(self, name, user, message=None, date=None):
175 175 """
176 176 Removes tag with the given ``name``.
177 177
178 178 :param name: name of the tag to be removed
179 179 :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
180 180 :param message: message of the tag's removal commit
181 181 :param date: date of tag's removal commit
182 182
183 183 :raises TagDoesNotExistError: if tag with given name does not exists
184 184 """
185 185 if name not in self.tags:
186 186 raise TagDoesNotExistError("Tag %s does not exist" % name)
187 187 if message is None:
188 188 message = "Removed tag %s" % name
189 189 if date is None:
190 190 date = datetime.datetime.now().ctime()
191 191 local = False
192 192
193 193 try:
194 194 self._repo.tag(name, nullid, message, local, user, date)
195 195 self.tags = self._get_tags()
196 196 except Abort, e:
197 197 raise RepositoryError(e.message)
198 198
199 199 @LazyProperty
200 200 def bookmarks(self):
201 201 """
202 202 Get's bookmarks for this repository
203 203 """
204 204 return self._get_bookmarks()
205 205
206 206 def _get_bookmarks(self):
207 207 if self._empty:
208 208 return {}
209 209
210 210 sortkey = lambda ctx: ctx[0] # sort by name
211 211 _bookmarks = [(safe_unicode(n), hex(h),) for n, h in
212 212 self._repo._bookmarks.items()]
213 213 return OrderedDict(sorted(_bookmarks, key=sortkey, reverse=True))
214 214
215 215 def _get_all_revisions(self):
216 216
217 217 return map(lambda x: hex(x[7]), self._repo.changelog.index)[:-1]
218 218
219 219 def get_diff(self, rev1, rev2, path='', ignore_whitespace=False,
220 220 context=3):
221 221 """
222 222 Returns (git like) *diff*, as plain text. Shows changes introduced by
223 223 ``rev2`` since ``rev1``.
224 224
225 225 :param rev1: Entry point from which diff is shown. Can be
226 226 ``self.EMPTY_CHANGESET`` - in this case, patch showing all
227 227 the changes since empty state of the repository until ``rev2``
228 228 :param rev2: Until which revision changes should be shown.
229 229 :param ignore_whitespace: If set to ``True``, would not show whitespace
230 230 changes. Defaults to ``False``.
231 231 :param context: How many lines before/after changed lines should be
232 232 shown. Defaults to ``3``.
233 233 """
234 234 if hasattr(rev1, 'raw_id'):
235 235 rev1 = getattr(rev1, 'raw_id')
236 236
237 237 if hasattr(rev2, 'raw_id'):
238 238 rev2 = getattr(rev2, 'raw_id')
239 239
240 240 # Check if given revisions are present at repository (may raise
241 241 # ChangesetDoesNotExistError)
242 242 if rev1 != self.EMPTY_CHANGESET:
243 243 self.get_changeset(rev1)
244 244 self.get_changeset(rev2)
245 if path:
246 file_filter = match(self.path, '', [path])
247 else:
248 file_filter = None
245 249
246 file_filter = match(self.path, '', [path])
247 250 return ''.join(patch.diff(self._repo, rev1, rev2, match=file_filter,
248 251 opts=diffopts(git=True,
249 252 ignorews=ignore_whitespace,
250 253 context=context)))
251 254
252 255 @classmethod
253 256 def _check_url(cls, url):
254 257 """
255 258 Function will check given url and try to verify if it's a valid
256 259 link. Sometimes it may happened that mercurial will issue basic
257 260 auth request that can cause whole API to hang when used from python
258 261 or other external calls.
259 262
260 263 On failures it'll raise urllib2.HTTPError, return code 200 if url
261 264 is valid or True if it's a local path
262 265 """
263 266
264 267 from mercurial.util import url as Url
265 268
266 269 # those authnadlers are patched for python 2.6.5 bug an
267 270 # infinit looping when given invalid resources
268 271 from mercurial.url import httpbasicauthhandler, httpdigestauthhandler
269 272
270 273 # check first if it's not an local url
271 274 if os.path.isdir(url) or url.startswith('file:'):
272 275 return True
273 276
274 277 if('+' in url[:url.find('://')]):
275 278 url = url[url.find('+') + 1:]
276 279
277 280 handlers = []
278 281 test_uri, authinfo = Url(url).authinfo()
279 282
280 283 if authinfo:
281 284 #create a password manager
282 285 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
283 286 passmgr.add_password(*authinfo)
284 287
285 288 handlers.extend((httpbasicauthhandler(passmgr),
286 289 httpdigestauthhandler(passmgr)))
287 290
288 291 o = urllib2.build_opener(*handlers)
289 292 o.addheaders = [('Content-Type', 'application/mercurial-0.1'),
290 293 ('Accept', 'application/mercurial-0.1')]
291 294
292 295 q = {"cmd": 'between'}
293 296 q.update({'pairs': "%s-%s" % ('0' * 40, '0' * 40)})
294 297 qs = '?%s' % urllib.urlencode(q)
295 298 cu = "%s%s" % (test_uri, qs)
296 299 req = urllib2.Request(cu, None, {})
297 300
298 301 try:
299 302 resp = o.open(req)
300 303 return resp.code == 200
301 304 except Exception, e:
302 305 # means it cannot be cloned
303 306 raise urllib2.URLError("[%s] %s" % (url, e))
304 307
305 308 def _get_repo(self, create, src_url=None, update_after_clone=False):
306 309 """
307 310 Function will check for mercurial repository in given path and return
308 311 a localrepo object. If there is no repository in that path it will
309 312 raise an exception unless ``create`` parameter is set to True - in
310 313 that case repository would be created and returned.
311 314 If ``src_url`` is given, would try to clone repository from the
312 315 location at given clone_point. Additionally it'll make update to
313 316 working copy accordingly to ``update_after_clone`` flag
314 317 """
315 318
316 319 try:
317 320 if src_url:
318 321 url = str(self._get_url(src_url))
319 322 opts = {}
320 323 if not update_after_clone:
321 324 opts.update({'noupdate': True})
322 325 try:
323 326 MercurialRepository._check_url(url)
324 327 clone(self.baseui, url, self.path, **opts)
325 328 # except urllib2.URLError:
326 329 # raise Abort("Got HTTP 404 error")
327 330 except Exception:
328 331 raise
329 332
330 333 # Don't try to create if we've already cloned repo
331 334 create = False
332 335 return localrepository(self.baseui, self.path, create=create)
333 336 except (Abort, RepoError), err:
334 337 if create:
335 338 msg = "Cannot create repository at %s. Original error was %s"\
336 339 % (self.path, err)
337 340 else:
338 341 msg = "Not valid repository at %s. Original error was %s"\
339 342 % (self.path, err)
340 343 raise RepositoryError(msg)
341 344
342 345 @LazyProperty
343 346 def in_memory_changeset(self):
344 347 return MercurialInMemoryChangeset(self)
345 348
346 349 @LazyProperty
347 350 def description(self):
348 351 undefined_description = u'unknown'
349 352 return safe_unicode(self._repo.ui.config('web', 'description',
350 353 undefined_description, untrusted=True))
351 354
352 355 @LazyProperty
353 356 def contact(self):
354 357 undefined_contact = u'Unknown'
355 358 return safe_unicode(get_contact(self._repo.ui.config)
356 359 or undefined_contact)
357 360
358 361 @LazyProperty
359 362 def last_change(self):
360 363 """
361 364 Returns last change made on this repository as datetime object
362 365 """
363 366 return date_fromtimestamp(self._get_mtime(), makedate()[1])
364 367
365 368 def _get_mtime(self):
366 369 try:
367 370 return time.mktime(self.get_changeset().date.timetuple())
368 371 except RepositoryError:
369 372 #fallback to filesystem
370 373 cl_path = os.path.join(self.path, '.hg', "00changelog.i")
371 374 st_path = os.path.join(self.path, '.hg', "store")
372 375 if os.path.exists(cl_path):
373 376 return os.stat(cl_path).st_mtime
374 377 else:
375 378 return os.stat(st_path).st_mtime
376 379
377 380 def _get_hidden(self):
378 381 return self._repo.ui.configbool("web", "hidden", untrusted=True)
379 382
380 383 def _get_revision(self, revision):
381 384 """
382 385 Get's an ID revision given as str. This will always return a fill
383 386 40 char revision number
384 387
385 388 :param revision: str or int or None
386 389 """
387 390
388 391 if self._empty:
389 392 raise EmptyRepositoryError("There are no changesets yet")
390 393
391 394 if revision in [-1, 'tip', None]:
392 395 revision = 'tip'
393 396
394 397 try:
395 398 revision = hex(self._repo.lookup(revision))
396 399 except (IndexError, ValueError, RepoLookupError, TypeError):
397 400 raise ChangesetDoesNotExistError("Revision %r does not "
398 401 "exist for this repository %s" \
399 402 % (revision, self))
400 403 return revision
401 404
402 405 def _get_archives(self, archive_name='tip'):
403 406 allowed = self.baseui.configlist("web", "allow_archive",
404 407 untrusted=True)
405 408 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
406 409 if i[0] in allowed or self._repo.ui.configbool("web",
407 410 "allow" + i[0],
408 411 untrusted=True):
409 412 yield {"type": i[0], "extension": i[1], "node": archive_name}
410 413
411 414 def _get_url(self, url):
412 415 """
413 416 Returns normalized url. If schema is not given, would fall
414 417 to filesystem
415 418 (``file:///``) schema.
416 419 """
417 420 url = str(url)
418 421 if url != 'default' and not '://' in url:
419 422 url = "file:" + urllib.pathname2url(url)
420 423 return url
421 424
422 425 def get_changeset(self, revision=None):
423 426 """
424 427 Returns ``MercurialChangeset`` object representing repository's
425 428 changeset at the given ``revision``.
426 429 """
427 430 revision = self._get_revision(revision)
428 431 changeset = MercurialChangeset(repository=self, revision=revision)
429 432 return changeset
430 433
431 434 def get_changesets(self, start=None, end=None, start_date=None,
432 435 end_date=None, branch_name=None, reverse=False):
433 436 """
434 437 Returns iterator of ``MercurialChangeset`` objects from start to end
435 438 (both are inclusive)
436 439
437 440 :param start: None, str, int or mercurial lookup format
438 441 :param end: None, str, int or mercurial lookup format
439 442 :param start_date:
440 443 :param end_date:
441 444 :param branch_name:
442 445 :param reversed: return changesets in reversed order
443 446 """
444 447
445 448 start_raw_id = self._get_revision(start)
446 449 start_pos = self.revisions.index(start_raw_id) if start else None
447 450 end_raw_id = self._get_revision(end)
448 451 end_pos = self.revisions.index(end_raw_id) if end else None
449 452
450 453 if None not in [start, end] and start_pos > end_pos:
451 454 raise RepositoryError("start revision '%s' cannot be "
452 455 "after end revision '%s'" % (start, end))
453 456
454 457 if branch_name and branch_name not in self.branches.keys():
455 458 raise BranchDoesNotExistError('Such branch %s does not exists for'
456 459 ' this repository' % branch_name)
457 460 if end_pos is not None:
458 461 end_pos += 1
459 462
460 463 slice_ = reversed(self.revisions[start_pos:end_pos]) if reverse else \
461 464 self.revisions[start_pos:end_pos]
462 465
463 466 for id_ in slice_:
464 467 cs = self.get_changeset(id_)
465 468 if branch_name and cs.branch != branch_name:
466 469 continue
467 470 if start_date and cs.date < start_date:
468 471 continue
469 472 if end_date and cs.date > end_date:
470 473 continue
471 474
472 475 yield cs
473 476
474 477 def pull(self, url):
475 478 """
476 479 Tries to pull changes from external location.
477 480 """
478 481 url = self._get_url(url)
479 482 try:
480 483 pull(self.baseui, self._repo, url)
481 484 except Abort, err:
482 485 # Propagate error but with vcs's type
483 486 raise RepositoryError(str(err))
484 487
485 488 @LazyProperty
486 489 def workdir(self):
487 490 """
488 491 Returns ``Workdir`` instance for this repository.
489 492 """
490 493 return MercurialWorkdir(self)
491 494
492 495 def get_config_value(self, section, name, config_file=None):
493 496 """
494 497 Returns configuration value for a given [``section``] and ``name``.
495 498
496 499 :param section: Section we want to retrieve value from
497 500 :param name: Name of configuration we want to retrieve
498 501 :param config_file: A path to file which should be used to retrieve
499 502 configuration from (might also be a list of file paths)
500 503 """
501 504 if config_file is None:
502 505 config_file = []
503 506 elif isinstance(config_file, basestring):
504 507 config_file = [config_file]
505 508
506 509 config = self._repo.ui
507 510 for path in config_file:
508 511 config.readconfig(path)
509 512 return config.config(section, name)
510 513
511 514 def get_user_name(self, config_file=None):
512 515 """
513 516 Returns user's name from global configuration file.
514 517
515 518 :param config_file: A path to file which should be used to retrieve
516 519 configuration from (might also be a list of file paths)
517 520 """
518 521 username = self.get_config_value('ui', 'username')
519 522 if username:
520 523 return author_name(username)
521 524 return None
522 525
523 526 def get_user_email(self, config_file=None):
524 527 """
525 528 Returns user's email from global configuration file.
526 529
527 530 :param config_file: A path to file which should be used to retrieve
528 531 configuration from (might also be a list of file paths)
529 532 """
530 533 username = self.get_config_value('ui', 'username')
531 534 if username:
532 535 return author_email(username)
533 536 return None
@@ -1,4715 +1,4751 b''
1 1 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td
2 2 {
3 3 border: 0;
4 4 outline: 0;
5 5 font-size: 100%;
6 6 vertical-align: baseline;
7 7 background: transparent;
8 8 margin: 0;
9 9 padding: 0;
10 10 }
11 11
12 12 body {
13 13 line-height: 1;
14 14 height: 100%;
15 15 background: url("../images/background.png") repeat scroll 0 0 #B0B0B0;
16 16 font-family: Lucida Grande, Verdana, Lucida Sans Regular,
17 17 Lucida Sans Unicode, Arial, sans-serif; font-size : 12px;
18 18 color: #000;
19 19 margin: 0;
20 20 padding: 0;
21 21 font-size: 12px;
22 22 }
23 23
24 24 ol,ul {
25 25 list-style: none;
26 26 }
27 27
28 28 blockquote,q {
29 29 quotes: none;
30 30 }
31 31
32 32 blockquote:before,blockquote:after,q:before,q:after {
33 33 content: none;
34 34 }
35 35
36 36 :focus {
37 37 outline: 0;
38 38 }
39 39
40 40 del {
41 41 text-decoration: line-through;
42 42 }
43 43
44 44 table {
45 45 border-collapse: collapse;
46 46 border-spacing: 0;
47 47 }
48 48
49 49 html {
50 50 height: 100%;
51 51 }
52 52
53 53 a {
54 54 color: #003367;
55 55 text-decoration: none;
56 56 cursor: pointer;
57 57 }
58 58
59 59 a:hover {
60 60 color: #316293;
61 61 text-decoration: underline;
62 62 }
63 63
64 64 h1,h2,h3,h4,h5,h6,
65 65 div.h1,div.h2,div.h3,div.h4,div.h5,div.h6 {
66 66 color: #292929;
67 67 font-weight: 700;
68 68 }
69 69
70 70 h1,div.h1 {
71 71 font-size: 22px;
72 72 }
73 73
74 74 h2,div.h2 {
75 75 font-size: 20px;
76 76 }
77 77
78 78 h3,div.h3 {
79 79 font-size: 18px;
80 80 }
81 81
82 82 h4,div.h4 {
83 83 font-size: 16px;
84 84 }
85 85
86 86 h5,div.h5 {
87 87 font-size: 14px;
88 88 }
89 89
90 90 h6,div.h6 {
91 91 font-size: 11px;
92 92 }
93 93
94 94 ul.circle {
95 95 list-style-type: circle;
96 96 }
97 97
98 98 ul.disc {
99 99 list-style-type: disc;
100 100 }
101 101
102 102 ul.square {
103 103 list-style-type: square;
104 104 }
105 105
106 106 ol.lower-roman {
107 107 list-style-type: lower-roman;
108 108 }
109 109
110 110 ol.upper-roman {
111 111 list-style-type: upper-roman;
112 112 }
113 113
114 114 ol.lower-alpha {
115 115 list-style-type: lower-alpha;
116 116 }
117 117
118 118 ol.upper-alpha {
119 119 list-style-type: upper-alpha;
120 120 }
121 121
122 122 ol.decimal {
123 123 list-style-type: decimal;
124 124 }
125 125
126 126 div.color {
127 127 clear: both;
128 128 overflow: hidden;
129 129 position: absolute;
130 130 background: #FFF;
131 131 margin: 7px 0 0 60px;
132 132 padding: 1px 1px 1px 0;
133 133 }
134 134
135 135 div.color a {
136 136 width: 15px;
137 137 height: 15px;
138 138 display: block;
139 139 float: left;
140 140 margin: 0 0 0 1px;
141 141 padding: 0;
142 142 }
143 143
144 144 div.options {
145 145 clear: both;
146 146 overflow: hidden;
147 147 position: absolute;
148 148 background: #FFF;
149 149 margin: 7px 0 0 162px;
150 150 padding: 0;
151 151 }
152 152
153 153 div.options a {
154 154 height: 1%;
155 155 display: block;
156 156 text-decoration: none;
157 157 margin: 0;
158 158 padding: 3px 8px;
159 159 }
160 160
161 161 .top-left-rounded-corner {
162 162 -webkit-border-top-left-radius: 8px;
163 163 -khtml-border-radius-topleft: 8px;
164 164 -moz-border-radius-topleft: 8px;
165 165 border-top-left-radius: 8px;
166 166 }
167 167
168 168 .top-right-rounded-corner {
169 169 -webkit-border-top-right-radius: 8px;
170 170 -khtml-border-radius-topright: 8px;
171 171 -moz-border-radius-topright: 8px;
172 172 border-top-right-radius: 8px;
173 173 }
174 174
175 175 .bottom-left-rounded-corner {
176 176 -webkit-border-bottom-left-radius: 8px;
177 177 -khtml-border-radius-bottomleft: 8px;
178 178 -moz-border-radius-bottomleft: 8px;
179 179 border-bottom-left-radius: 8px;
180 180 }
181 181
182 182 .bottom-right-rounded-corner {
183 183 -webkit-border-bottom-right-radius: 8px;
184 184 -khtml-border-radius-bottomright: 8px;
185 185 -moz-border-radius-bottomright: 8px;
186 186 border-bottom-right-radius: 8px;
187 187 }
188 188
189 189 .top-left-rounded-corner-mid {
190 190 -webkit-border-top-left-radius: 4px;
191 191 -khtml-border-radius-topleft: 4px;
192 192 -moz-border-radius-topleft: 4px;
193 193 border-top-left-radius: 4px;
194 194 }
195 195
196 196 .top-right-rounded-corner-mid {
197 197 -webkit-border-top-right-radius: 4px;
198 198 -khtml-border-radius-topright: 4px;
199 199 -moz-border-radius-topright: 4px;
200 200 border-top-right-radius: 4px;
201 201 }
202 202
203 203 .bottom-left-rounded-corner-mid {
204 204 -webkit-border-bottom-left-radius: 4px;
205 205 -khtml-border-radius-bottomleft: 4px;
206 206 -moz-border-radius-bottomleft: 4px;
207 207 border-bottom-left-radius: 4px;
208 208 }
209 209
210 210 .bottom-right-rounded-corner-mid {
211 211 -webkit-border-bottom-right-radius: 4px;
212 212 -khtml-border-radius-bottomright: 4px;
213 213 -moz-border-radius-bottomright: 4px;
214 214 border-bottom-right-radius: 4px;
215 215 }
216 216
217 217 .help-block {
218 218 color: #999999;
219 219 display: block;
220 220 margin-bottom: 0;
221 221 margin-top: 5px;
222 222 }
223 223
224 224 .empty_data{
225 225 color:#B9B9B9;
226 226 }
227 227
228 228 a.permalink{
229 229 visibility: hidden;
230 230 }
231 231
232 232 a.permalink:hover{
233 233 text-decoration: none;
234 234 }
235 235
236 236 h1:hover > a.permalink,
237 237 h2:hover > a.permalink,
238 238 h3:hover > a.permalink,
239 239 h4:hover > a.permalink,
240 240 h5:hover > a.permalink,
241 241 h6:hover > a.permalink,
242 242 div:hover > a.permalink {
243 243 visibility: visible;
244 244 }
245 245
246 246 #header {
247 247 margin: 0;
248 248 padding: 0 10px;
249 249 }
250 250
251 251 #header ul#logged-user {
252 252 margin-bottom: 5px !important;
253 253 -webkit-border-radius: 0px 0px 8px 8px;
254 254 -khtml-border-radius: 0px 0px 8px 8px;
255 255 -moz-border-radius: 0px 0px 8px 8px;
256 256 border-radius: 0px 0px 8px 8px;
257 257 height: 37px;
258 258 background-color: #003B76;
259 259 background-repeat: repeat-x;
260 260 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
261 261 background-image: -moz-linear-gradient(top, #003b76, #00376e);
262 262 background-image: -ms-linear-gradient(top, #003b76, #00376e);
263 263 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
264 264 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
265 265 background-image: -o-linear-gradient(top, #003b76, #00376e);
266 266 background-image: linear-gradient(top, #003b76, #00376e);
267 267 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
268 268 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
269 269 }
270 270
271 271 #header ul#logged-user li {
272 272 list-style: none;
273 273 float: left;
274 274 margin: 8px 0 0;
275 275 padding: 4px 12px;
276 276 border-left: 1px solid #316293;
277 277 }
278 278
279 279 #header ul#logged-user li.first {
280 280 border-left: none;
281 281 margin: 4px;
282 282 }
283 283
284 284 #header ul#logged-user li.first div.gravatar {
285 285 margin-top: -2px;
286 286 }
287 287
288 288 #header ul#logged-user li.first div.account {
289 289 padding-top: 4px;
290 290 float: left;
291 291 }
292 292
293 293 #header ul#logged-user li.last {
294 294 border-right: none;
295 295 }
296 296
297 297 #header ul#logged-user li a {
298 298 color: #fff;
299 299 font-weight: 700;
300 300 text-decoration: none;
301 301 }
302 302
303 303 #header ul#logged-user li a:hover {
304 304 text-decoration: underline;
305 305 }
306 306
307 307 #header ul#logged-user li.highlight a {
308 308 color: #fff;
309 309 }
310 310
311 311 #header ul#logged-user li.highlight a:hover {
312 312 color: #FFF;
313 313 }
314 314
315 315 #header #header-inner {
316 316 min-height: 44px;
317 317 clear: both;
318 318 position: relative;
319 319 background-color: #003B76;
320 320 background-repeat: repeat-x;
321 321 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
322 322 background-image: -moz-linear-gradient(top, #003b76, #00376e);
323 323 background-image: -ms-linear-gradient(top, #003b76, #00376e);
324 324 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),color-stop(100%, #00376e) );
325 325 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
326 326 background-image: -o-linear-gradient(top, #003b76, #00376e);
327 327 background-image: linear-gradient(top, #003b76, #00376e);
328 328 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
329 329 margin: 0;
330 330 padding: 0;
331 331 display: block;
332 332 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
333 333 -webkit-border-radius: 4px 4px 4px 4px;
334 334 -khtml-border-radius: 4px 4px 4px 4px;
335 335 -moz-border-radius: 4px 4px 4px 4px;
336 336 border-radius: 4px 4px 4px 4px;
337 337 }
338 338 #header #header-inner.hover{
339 339 position: fixed !important;
340 340 width: 100% !important;
341 341 margin-left: -10px !important;
342 342 z-index: 10000;
343 343 -webkit-border-radius: 0px 0px 0px 0px;
344 344 -khtml-border-radius: 0px 0px 0px 0px;
345 345 -moz-border-radius: 0px 0px 0px 0px;
346 346 border-radius: 0px 0px 0px 0px;
347 347 }
348 348
349 349 .ie7 #header #header-inner.hover,
350 350 .ie8 #header #header-inner.hover,
351 351 .ie9 #header #header-inner.hover
352 352 {
353 353 z-index: auto !important;
354 354 }
355 355
356 356 .header-pos-fix, .anchor{
357 357 margin-top: -46px;
358 358 padding-top: 46px;
359 359 }
360 360
361 361 #header #header-inner #home a {
362 362 height: 40px;
363 363 width: 46px;
364 364 display: block;
365 365 background: url("../images/button_home.png");
366 366 background-position: 0 0;
367 367 margin: 0;
368 368 padding: 0;
369 369 }
370 370
371 371 #header #header-inner #home a:hover {
372 372 background-position: 0 -40px;
373 373 }
374 374
375 375 #header #header-inner #logo {
376 376 float: left;
377 377 position: absolute;
378 378 }
379 379
380 380 #header #header-inner #logo h1 {
381 381 color: #FFF;
382 382 font-size: 20px;
383 383 margin: 12px 0 0 13px;
384 384 padding: 0;
385 385 }
386 386
387 387 #header #header-inner #logo a {
388 388 color: #fff;
389 389 text-decoration: none;
390 390 }
391 391
392 392 #header #header-inner #logo a:hover {
393 393 color: #bfe3ff;
394 394 }
395 395
396 396 #header #header-inner #quick,#header #header-inner #quick ul {
397 397 position: relative;
398 398 float: right;
399 399 list-style-type: none;
400 400 list-style-position: outside;
401 401 margin: 8px 8px 0 0;
402 402 padding: 0;
403 403 }
404 404
405 405 #header #header-inner #quick li {
406 406 position: relative;
407 407 float: left;
408 408 margin: 0 5px 0 0;
409 409 padding: 0;
410 410 }
411 411
412 412 #header #header-inner #quick li a.menu_link {
413 413 top: 0;
414 414 left: 0;
415 415 height: 1%;
416 416 display: block;
417 417 clear: both;
418 418 overflow: hidden;
419 419 color: #FFF;
420 420 font-weight: 700;
421 421 text-decoration: none;
422 422 background: #369;
423 423 padding: 0;
424 424 -webkit-border-radius: 4px 4px 4px 4px;
425 425 -khtml-border-radius: 4px 4px 4px 4px;
426 426 -moz-border-radius: 4px 4px 4px 4px;
427 427 border-radius: 4px 4px 4px 4px;
428 428 }
429 429
430 430 #header #header-inner #quick li span.short {
431 431 padding: 9px 6px 8px 6px;
432 432 }
433 433
434 434 #header #header-inner #quick li span {
435 435 top: 0;
436 436 right: 0;
437 437 height: 1%;
438 438 display: block;
439 439 float: left;
440 440 border-left: 1px solid #3f6f9f;
441 441 margin: 0;
442 442 padding: 10px 12px 8px 10px;
443 443 }
444 444
445 445 #header #header-inner #quick li span.normal {
446 446 border: none;
447 447 padding: 10px 12px 8px;
448 448 }
449 449
450 450 #header #header-inner #quick li span.icon {
451 451 top: 0;
452 452 left: 0;
453 453 border-left: none;
454 454 border-right: 1px solid #2e5c89;
455 455 padding: 8px 6px 4px;
456 456 }
457 457
458 458 #header #header-inner #quick li span.icon_short {
459 459 top: 0;
460 460 left: 0;
461 461 border-left: none;
462 462 border-right: 1px solid #2e5c89;
463 463 padding: 8px 6px 4px;
464 464 }
465 465
466 466 #header #header-inner #quick li span.icon img,#header #header-inner #quick li span.icon_short img
467 467 {
468 468 margin: 0px -2px 0px 0px;
469 469 }
470 470
471 471 #header #header-inner #quick li a:hover {
472 472 background: #4e4e4e no-repeat top left;
473 473 }
474 474
475 475 #header #header-inner #quick li a:hover span {
476 476 border-left: 1px solid #545454;
477 477 }
478 478
479 479 #header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short
480 480 {
481 481 border-left: none;
482 482 border-right: 1px solid #464646;
483 483 }
484 484
485 485 #header #header-inner #quick ul {
486 486 top: 29px;
487 487 right: 0;
488 488 min-width: 200px;
489 489 display: none;
490 490 position: absolute;
491 491 background: #FFF;
492 492 border: 1px solid #666;
493 493 border-top: 1px solid #003367;
494 494 z-index: 100;
495 495 margin: 0px 0px 0px 0px;
496 496 padding: 0;
497 497 }
498 498
499 499 #header #header-inner #quick ul.repo_switcher {
500 500 max-height: 275px;
501 501 overflow-x: hidden;
502 502 overflow-y: auto;
503 503 }
504 504
505 505 #header #header-inner #quick ul.repo_switcher li.qfilter_rs {
506 506 float: none;
507 507 margin: 0;
508 508 border-bottom: 2px solid #003367;
509 509 }
510 510
511 511 #header #header-inner #quick .repo_switcher_type {
512 512 position: absolute;
513 513 left: 0;
514 514 top: 9px;
515 515 }
516 516
517 517 #header #header-inner #quick li ul li {
518 518 border-bottom: 1px solid #ddd;
519 519 }
520 520
521 521 #header #header-inner #quick li ul li a {
522 522 width: 182px;
523 523 height: auto;
524 524 display: block;
525 525 float: left;
526 526 background: #FFF;
527 527 color: #003367;
528 528 font-weight: 400;
529 529 margin: 0;
530 530 padding: 7px 9px;
531 531 }
532 532
533 533 #header #header-inner #quick li ul li a:hover {
534 534 color: #000;
535 535 background: #FFF;
536 536 }
537 537
538 538 #header #header-inner #quick ul ul {
539 539 top: auto;
540 540 }
541 541
542 542 #header #header-inner #quick li ul ul {
543 543 right: 200px;
544 544 max-height: 275px;
545 545 overflow: auto;
546 546 overflow-x: hidden;
547 547 white-space: normal;
548 548 }
549 549
550 550 #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover
551 551 {
552 552 background: url("../images/icons/book.png") no-repeat scroll 4px 9px
553 553 #FFF;
554 554 width: 167px;
555 555 margin: 0;
556 556 padding: 12px 9px 7px 24px;
557 557 }
558 558
559 559 #header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover
560 560 {
561 561 background: url("../images/icons/lock.png") no-repeat scroll 4px 9px
562 562 #FFF;
563 563 min-width: 167px;
564 564 margin: 0;
565 565 padding: 12px 9px 7px 24px;
566 566 }
567 567
568 568 #header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover
569 569 {
570 570 background: url("../images/icons/lock_open.png") no-repeat scroll 4px
571 571 9px #FFF;
572 572 min-width: 167px;
573 573 margin: 0;
574 574 padding: 12px 9px 7px 24px;
575 575 }
576 576
577 577 #header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover
578 578 {
579 579 background: url("../images/icons/hgicon.png") no-repeat scroll 4px 9px
580 580 #FFF;
581 581 min-width: 167px;
582 582 margin: 0 0 0 14px;
583 583 padding: 12px 9px 7px 24px;
584 584 }
585 585
586 586 #header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover
587 587 {
588 588 background: url("../images/icons/giticon.png") no-repeat scroll 4px 9px
589 589 #FFF;
590 590 min-width: 167px;
591 591 margin: 0 0 0 14px;
592 592 padding: 12px 9px 7px 24px;
593 593 }
594 594
595 595 #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover
596 596 {
597 597 background: url("../images/icons/database_edit.png") no-repeat scroll
598 598 4px 9px #FFF;
599 599 width: 167px;
600 600 margin: 0;
601 601 padding: 12px 9px 7px 24px;
602 602 }
603 603
604 604 #header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover
605 605 {
606 606 background: url("../images/icons/database_link.png") no-repeat scroll
607 607 4px 9px #FFF;
608 608 width: 167px;
609 609 margin: 0;
610 610 padding: 12px 9px 7px 24px;
611 611 }
612 612
613 613 #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover
614 614 {
615 615 background: #FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
616 616 width: 167px;
617 617 margin: 0;
618 618 padding: 12px 9px 7px 24px;
619 619 }
620 620
621 621 #header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover
622 622 {
623 623 background: #FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
624 624 width: 167px;
625 625 margin: 0;
626 626 padding: 12px 9px 7px 24px;
627 627 }
628 628
629 629 #header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover
630 630 {
631 631 background: #FFF url("../images/icons/cog.png") no-repeat 4px 9px;
632 632 width: 167px;
633 633 margin: 0;
634 634 padding: 12px 9px 7px 24px;
635 635 }
636 636
637 637 #header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover
638 638 {
639 639 background: #FFF url("../images/icons/key.png") no-repeat 4px 9px;
640 640 width: 167px;
641 641 margin: 0;
642 642 padding: 12px 9px 7px 24px;
643 643 }
644 644
645 645 #header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover
646 646 {
647 647 background: #FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
648 648 width: 167px;
649 649 margin: 0;
650 650 padding: 12px 9px 7px 24px;
651 651 }
652 652
653 653 #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover
654 654 {
655 655 background: #FFF url("../images/icons/arrow_divide.png") no-repeat 4px
656 656 9px;
657 657 width: 167px;
658 658 margin: 0;
659 659 padding: 12px 9px 7px 24px;
660 660 }
661 661
662 662 #header #header-inner #quick li ul li a.locking_add,#header #header-inner #quick li ul li a.locking_add:hover
663 663 {
664 664 background: #FFF url("../images/icons/lock_add.png") no-repeat 4px
665 665 9px;
666 666 width: 167px;
667 667 margin: 0;
668 668 padding: 12px 9px 7px 24px;
669 669 }
670 670
671 671 #header #header-inner #quick li ul li a.locking_del,#header #header-inner #quick li ul li a.locking_del:hover
672 672 {
673 673 background: #FFF url("../images/icons/lock_delete.png") no-repeat 4px
674 674 9px;
675 675 width: 167px;
676 676 margin: 0;
677 677 padding: 12px 9px 7px 24px;
678 678 }
679 679
680 680 #header #header-inner #quick li ul li a.pull_request,#header #header-inner #quick li ul li a.pull_request:hover
681 681 {
682 682 background: #FFF url("../images/icons/arrow_join.png") no-repeat 4px
683 683 9px;
684 684 width: 167px;
685 685 margin: 0;
686 686 padding: 12px 9px 7px 24px;
687 687 }
688 688
689 689 #header #header-inner #quick li ul li a.compare_request,#header #header-inner #quick li ul li a.compare_request:hover
690 690 {
691 691 background: #FFF url("../images/icons/arrow_inout.png") no-repeat 4px
692 692 9px;
693 693 width: 167px;
694 694 margin: 0;
695 695 padding: 12px 9px 7px 24px;
696 696 }
697 697
698 698 #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover
699 699 {
700 700 background: #FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
701 701 width: 167px;
702 702 margin: 0;
703 703 padding: 12px 9px 7px 24px;
704 704 }
705 705
706 706 #header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover
707 707 {
708 708 background: #FFF url("../images/icons/delete.png") no-repeat 4px 9px;
709 709 width: 167px;
710 710 margin: 0;
711 711 padding: 12px 9px 7px 24px;
712 712 }
713 713
714 714 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover
715 715 {
716 716 background: #FFF url("../images/icons/arrow_branch.png") no-repeat 4px
717 717 9px;
718 718 width: 167px;
719 719 margin: 0;
720 720 padding: 12px 9px 7px 24px;
721 721 }
722 722
723 723 #header #header-inner #quick li ul li a.tags,
724 724 #header #header-inner #quick li ul li a.tags:hover{
725 725 background: #FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
726 726 width: 167px;
727 727 margin: 0;
728 728 padding: 12px 9px 7px 24px;
729 729 }
730 730
731 731 #header #header-inner #quick li ul li a.bookmarks,
732 732 #header #header-inner #quick li ul li a.bookmarks:hover{
733 733 background: #FFF url("../images/icons/tag_green.png") no-repeat 4px 9px;
734 734 width: 167px;
735 735 margin: 0;
736 736 padding: 12px 9px 7px 24px;
737 737 }
738 738
739 739 #header #header-inner #quick li ul li a.admin,
740 740 #header #header-inner #quick li ul li a.admin:hover{
741 741 background: #FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
742 742 width: 167px;
743 743 margin: 0;
744 744 padding: 12px 9px 7px 24px;
745 745 }
746 746
747 747 .groups_breadcrumbs a {
748 748 color: #fff;
749 749 }
750 750
751 751 .groups_breadcrumbs a:hover {
752 752 color: #bfe3ff;
753 753 text-decoration: none;
754 754 }
755 755
756 756 td.quick_repo_menu {
757 757 background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
758 758 cursor: pointer;
759 759 width: 8px;
760 760 border: 1px solid transparent;
761 761 }
762 762
763 763 td.quick_repo_menu.active {
764 764 background: url("../images/dt-arrow-dn.png") no-repeat scroll 5px 50% #FFFFFF !important;
765 765 border: 1px solid #003367;
766 766 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
767 767 cursor: pointer;
768 768 }
769 769
770 770 td.quick_repo_menu .menu_items {
771 771 margin-top: 10px;
772 772 margin-left:-6px;
773 773 width: 150px;
774 774 position: absolute;
775 775 background-color: #FFF;
776 776 background: none repeat scroll 0 0 #FFFFFF;
777 777 border-color: #003367 #666666 #666666;
778 778 border-right: 1px solid #666666;
779 779 border-style: solid;
780 780 border-width: 1px;
781 781 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
782 782 border-top-style: none;
783 783 }
784 784
785 785 td.quick_repo_menu .menu_items li {
786 786 padding: 0 !important;
787 787 }
788 788
789 789 td.quick_repo_menu .menu_items a {
790 790 display: block;
791 791 padding: 4px 12px 4px 8px;
792 792 }
793 793
794 794 td.quick_repo_menu .menu_items a:hover {
795 795 background-color: #EEE;
796 796 text-decoration: none;
797 797 }
798 798
799 799 td.quick_repo_menu .menu_items .icon img {
800 800 margin-bottom: -2px;
801 801 }
802 802
803 803 td.quick_repo_menu .menu_items.hidden {
804 804 display: none;
805 805 }
806 806
807 807 .yui-dt-first th {
808 808 text-align: left;
809 809 }
810 810
811 811 /*
812 812 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
813 813 Code licensed under the BSD License:
814 814 http://developer.yahoo.com/yui/license.html
815 815 version: 2.9.0
816 816 */
817 817 .yui-skin-sam .yui-dt-mask {
818 818 position: absolute;
819 819 z-index: 9500;
820 820 }
821 821 .yui-dt-tmp {
822 822 position: absolute;
823 823 left: -9000px;
824 824 }
825 825 .yui-dt-scrollable .yui-dt-bd { overflow: auto }
826 826 .yui-dt-scrollable .yui-dt-hd {
827 827 overflow: hidden;
828 828 position: relative;
829 829 }
830 830 .yui-dt-scrollable .yui-dt-bd thead tr,
831 831 .yui-dt-scrollable .yui-dt-bd thead th {
832 832 position: absolute;
833 833 left: -1500px;
834 834 }
835 835 .yui-dt-scrollable tbody { -moz-outline: 0 }
836 836 .yui-skin-sam thead .yui-dt-sortable { cursor: pointer }
837 837 .yui-skin-sam thead .yui-dt-draggable { cursor: move }
838 838 .yui-dt-coltarget {
839 839 position: absolute;
840 840 z-index: 999;
841 841 }
842 842 .yui-dt-hd { zoom: 1 }
843 843 th.yui-dt-resizeable .yui-dt-resizerliner { position: relative }
844 844 .yui-dt-resizer {
845 845 position: absolute;
846 846 right: 0;
847 847 bottom: 0;
848 848 height: 100%;
849 849 cursor: e-resize;
850 850 cursor: col-resize;
851 851 background-color: #CCC;
852 852 opacity: 0;
853 853 filter: alpha(opacity=0);
854 854 }
855 855 .yui-dt-resizerproxy {
856 856 visibility: hidden;
857 857 position: absolute;
858 858 z-index: 9000;
859 859 background-color: #CCC;
860 860 opacity: 0;
861 861 filter: alpha(opacity=0);
862 862 }
863 863 th.yui-dt-hidden .yui-dt-liner,
864 864 td.yui-dt-hidden .yui-dt-liner,
865 865 th.yui-dt-hidden .yui-dt-resizer { display: none }
866 866 .yui-dt-editor,
867 867 .yui-dt-editor-shim {
868 868 position: absolute;
869 869 z-index: 9000;
870 870 }
871 871 .yui-skin-sam .yui-dt table {
872 872 margin: 0;
873 873 padding: 0;
874 874 font-family: arial;
875 875 font-size: inherit;
876 876 border-collapse: separate;
877 877 *border-collapse: collapse;
878 878 border-spacing: 0;
879 879 border: 1px solid #7f7f7f;
880 880 }
881 881 .yui-skin-sam .yui-dt thead { border-spacing: 0 }
882 882 .yui-skin-sam .yui-dt caption {
883 883 color: #000;
884 884 font-size: 85%;
885 885 font-weight: normal;
886 886 font-style: italic;
887 887 line-height: 1;
888 888 padding: 1em 0;
889 889 text-align: center;
890 890 }
891 891 .yui-skin-sam .yui-dt th { background: #d8d8da url(../images/sprite.png) repeat-x 0 0 }
892 892 .yui-skin-sam .yui-dt th,
893 893 .yui-skin-sam .yui-dt th a {
894 894 font-weight: normal;
895 895 text-decoration: none;
896 896 color: #000;
897 897 vertical-align: bottom;
898 898 }
899 899 .yui-skin-sam .yui-dt th {
900 900 margin: 0;
901 901 padding: 0;
902 902 border: 0;
903 903 border-right: 1px solid #cbcbcb;
904 904 }
905 905 .yui-skin-sam .yui-dt tr.yui-dt-first td { border-top: 1px solid #7f7f7f }
906 906 .yui-skin-sam .yui-dt th .yui-dt-liner { white-space: nowrap }
907 907 .yui-skin-sam .yui-dt-liner {
908 908 margin: 0;
909 909 padding: 0;
910 910 }
911 911 .yui-skin-sam .yui-dt-coltarget {
912 912 width: 5px;
913 913 background-color: red;
914 914 }
915 915 .yui-skin-sam .yui-dt td {
916 916 margin: 0;
917 917 padding: 0;
918 918 border: 0;
919 919 border-right: 1px solid #cbcbcb;
920 920 text-align: left;
921 921 }
922 922 .yui-skin-sam .yui-dt-list td { border-right: 0 }
923 923 .yui-skin-sam .yui-dt-resizer { width: 6px }
924 924 .yui-skin-sam .yui-dt-mask {
925 925 background-color: #000;
926 926 opacity: .25;
927 927 filter: alpha(opacity=25);
928 928 }
929 929 .yui-skin-sam .yui-dt-message { background-color: #FFF }
930 930 .yui-skin-sam .yui-dt-scrollable table { border: 0 }
931 931 .yui-skin-sam .yui-dt-scrollable .yui-dt-hd {
932 932 border-left: 1px solid #7f7f7f;
933 933 border-top: 1px solid #7f7f7f;
934 934 border-right: 1px solid #7f7f7f;
935 935 }
936 936 .yui-skin-sam .yui-dt-scrollable .yui-dt-bd {
937 937 border-left: 1px solid #7f7f7f;
938 938 border-bottom: 1px solid #7f7f7f;
939 939 border-right: 1px solid #7f7f7f;
940 940 background-color: #FFF;
941 941 }
942 942 .yui-skin-sam .yui-dt-scrollable .yui-dt-data tr.yui-dt-last td { border-bottom: 1px solid #7f7f7f }
943 943 .yui-skin-sam th.yui-dt-asc,
944 944 .yui-skin-sam th.yui-dt-desc { background: url(../images/sprite.png) repeat-x 0 -100px }
945 945 .yui-skin-sam th.yui-dt-sortable .yui-dt-label { margin-right: 10px }
946 946 .yui-skin-sam th.yui-dt-asc .yui-dt-liner { background: url(../images/dt-arrow-up.png) no-repeat right }
947 947 .yui-skin-sam th.yui-dt-desc .yui-dt-liner { background: url(../images/dt-arrow-dn.png) no-repeat right }
948 948 tbody .yui-dt-editable { cursor: pointer }
949 949 .yui-dt-editor {
950 950 text-align: left;
951 951 background-color: #f2f2f2;
952 952 border: 1px solid #808080;
953 953 padding: 6px;
954 954 }
955 955 .yui-dt-editor label {
956 956 padding-left: 4px;
957 957 padding-right: 6px;
958 958 }
959 959 .yui-dt-editor .yui-dt-button {
960 960 padding-top: 6px;
961 961 text-align: right;
962 962 }
963 963 .yui-dt-editor .yui-dt-button button {
964 964 background: url(../images/sprite.png) repeat-x 0 0;
965 965 border: 1px solid #999;
966 966 width: 4em;
967 967 height: 1.8em;
968 968 margin-left: 6px;
969 969 }
970 970 .yui-dt-editor .yui-dt-button button.yui-dt-default {
971 971 background: url(../images/sprite.png) repeat-x 0 -1400px;
972 972 background-color: #5584e0;
973 973 border: 1px solid #304369;
974 974 color: #FFF;
975 975 }
976 976 .yui-dt-editor .yui-dt-button button:hover {
977 977 background: url(../images/sprite.png) repeat-x 0 -1300px;
978 978 color: #000;
979 979 }
980 980 .yui-dt-editor .yui-dt-button button:active {
981 981 background: url(../images/sprite.png) repeat-x 0 -1700px;
982 982 color: #000;
983 983 }
984 984 .yui-skin-sam tr.yui-dt-even { background-color: #FFF }
985 985 .yui-skin-sam tr.yui-dt-odd { background-color: #edf5ff }
986 986 .yui-skin-sam tr.yui-dt-even td.yui-dt-asc,
987 987 .yui-skin-sam tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
988 988 .yui-skin-sam tr.yui-dt-odd td.yui-dt-asc,
989 989 .yui-skin-sam tr.yui-dt-odd td.yui-dt-desc { background-color: #dbeaff }
990 990 .yui-skin-sam .yui-dt-list tr.yui-dt-even { background-color: #FFF }
991 991 .yui-skin-sam .yui-dt-list tr.yui-dt-odd { background-color: #FFF }
992 992 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-asc,
993 993 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
994 994 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-asc,
995 995 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-desc { background-color: #edf5ff }
996 996 .yui-skin-sam th.yui-dt-highlighted,
997 997 .yui-skin-sam th.yui-dt-highlighted a { background-color: #b2d2ff }
998 998 .yui-skin-sam tr.yui-dt-highlighted,
999 999 .yui-skin-sam tr.yui-dt-highlighted td.yui-dt-asc,
1000 1000 .yui-skin-sam tr.yui-dt-highlighted td.yui-dt-desc,
1001 1001 .yui-skin-sam tr.yui-dt-even td.yui-dt-highlighted,
1002 1002 .yui-skin-sam tr.yui-dt-odd td.yui-dt-highlighted {
1003 1003 cursor: pointer;
1004 1004 background-color: #b2d2ff;
1005 1005 }
1006 1006 .yui-skin-sam .yui-dt-list th.yui-dt-highlighted,
1007 1007 .yui-skin-sam .yui-dt-list th.yui-dt-highlighted a { background-color: #b2d2ff }
1008 1008 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted,
1009 1009 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-asc,
1010 1010 .yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-desc,
1011 1011 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-highlighted,
1012 1012 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-highlighted {
1013 1013 cursor: pointer;
1014 1014 background-color: #b2d2ff;
1015 1015 }
1016 1016 .yui-skin-sam th.yui-dt-selected,
1017 1017 .yui-skin-sam th.yui-dt-selected a { background-color: #446cd7 }
1018 1018 .yui-skin-sam tr.yui-dt-selected td,
1019 1019 .yui-skin-sam tr.yui-dt-selected td.yui-dt-asc,
1020 1020 .yui-skin-sam tr.yui-dt-selected td.yui-dt-desc {
1021 1021 background-color: #426fd9;
1022 1022 color: #FFF;
1023 1023 }
1024 1024 .yui-skin-sam tr.yui-dt-even td.yui-dt-selected,
1025 1025 .yui-skin-sam tr.yui-dt-odd td.yui-dt-selected {
1026 1026 background-color: #446cd7;
1027 1027 color: #FFF;
1028 1028 }
1029 1029 .yui-skin-sam .yui-dt-list th.yui-dt-selected,
1030 1030 .yui-skin-sam .yui-dt-list th.yui-dt-selected a { background-color: #446cd7 }
1031 1031 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td,
1032 1032 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-asc,
1033 1033 .yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-desc {
1034 1034 background-color: #426fd9;
1035 1035 color: #FFF;
1036 1036 }
1037 1037 .yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-selected,
1038 1038 .yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-selected {
1039 1039 background-color: #446cd7;
1040 1040 color: #FFF;
1041 1041 }
1042 1042 .yui-skin-sam .yui-dt-paginator {
1043 1043 display: block;
1044 1044 margin: 6px 0;
1045 1045 white-space: nowrap;
1046 1046 }
1047 1047 .yui-skin-sam .yui-dt-paginator .yui-dt-first,
1048 1048 .yui-skin-sam .yui-dt-paginator .yui-dt-last,
1049 1049 .yui-skin-sam .yui-dt-paginator .yui-dt-selected { padding: 2px 6px }
1050 1050 .yui-skin-sam .yui-dt-paginator a.yui-dt-first,
1051 1051 .yui-skin-sam .yui-dt-paginator a.yui-dt-last { text-decoration: none }
1052 1052 .yui-skin-sam .yui-dt-paginator .yui-dt-previous,
1053 1053 .yui-skin-sam .yui-dt-paginator .yui-dt-next { display: none }
1054 1054 .yui-skin-sam a.yui-dt-page {
1055 1055 border: 1px solid #cbcbcb;
1056 1056 padding: 2px 6px;
1057 1057 text-decoration: none;
1058 1058 background-color: #fff;
1059 1059 }
1060 1060 .yui-skin-sam .yui-dt-selected {
1061 1061 border: 1px solid #fff;
1062 1062 background-color: #fff;
1063 1063 }
1064 1064
1065 1065 #content #left {
1066 1066 left: 0;
1067 1067 width: 280px;
1068 1068 position: absolute;
1069 1069 }
1070 1070
1071 1071 #content #right {
1072 1072 margin: 0 60px 10px 290px;
1073 1073 }
1074 1074
1075 1075 #content div.box {
1076 1076 clear: both;
1077 1077 overflow: hidden;
1078 1078 background: #fff;
1079 1079 margin: 0 0 10px;
1080 1080 padding: 0 0 10px;
1081 1081 -webkit-border-radius: 4px 4px 4px 4px;
1082 1082 -khtml-border-radius: 4px 4px 4px 4px;
1083 1083 -moz-border-radius: 4px 4px 4px 4px;
1084 1084 border-radius: 4px 4px 4px 4px;
1085 1085 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1086 1086 }
1087 1087
1088 1088 #content div.box-left {
1089 1089 width: 49%;
1090 1090 clear: none;
1091 1091 float: left;
1092 1092 margin: 0 0 10px;
1093 1093 }
1094 1094
1095 1095 #content div.box-right {
1096 1096 width: 49%;
1097 1097 clear: none;
1098 1098 float: right;
1099 1099 margin: 0 0 10px;
1100 1100 }
1101 1101
1102 1102 #content div.box div.title {
1103 1103 clear: both;
1104 1104 overflow: hidden;
1105 1105 background-color: #003B76;
1106 1106 background-repeat: repeat-x;
1107 1107 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
1108 1108 background-image: -moz-linear-gradient(top, #003b76, #00376e);
1109 1109 background-image: -ms-linear-gradient(top, #003b76, #00376e);
1110 1110 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
1111 1111 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
1112 1112 background-image: -o-linear-gradient(top, #003b76, #00376e);
1113 1113 background-image: linear-gradient(top, #003b76, #00376e);
1114 1114 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
1115 1115 margin: 0 0 20px;
1116 1116 padding: 0;
1117 1117 }
1118 1118
1119 1119 #content div.box div.title h5 {
1120 1120 float: left;
1121 1121 border: none;
1122 1122 color: #fff;
1123 1123 text-transform: uppercase;
1124 1124 margin: 0;
1125 1125 padding: 11px 0 11px 10px;
1126 1126 }
1127 1127
1128 1128 #content div.box div.title .link-white{
1129 1129 color: #FFFFFF;
1130 1130 }
1131 1131
1132 1132 #content div.box div.title .link-white.current{
1133 1133 color: #BFE3FF;
1134 1134 }
1135 1135
1136 1136 #content div.box div.title ul.links li {
1137 1137 list-style: none;
1138 1138 float: left;
1139 1139 margin: 0;
1140 1140 padding: 0;
1141 1141 }
1142 1142
1143 1143 #content div.box div.title ul.links li a {
1144 1144 border-left: 1px solid #316293;
1145 1145 color: #FFFFFF;
1146 1146 display: block;
1147 1147 float: left;
1148 1148 font-size: 13px;
1149 1149 font-weight: 700;
1150 1150 height: 1%;
1151 1151 margin: 0;
1152 1152 padding: 11px 22px 12px;
1153 1153 text-decoration: none;
1154 1154 }
1155 1155
1156 1156 #content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6,
1157 1157 #content div.box div.h1,#content div.box div.h2,#content div.box div.h3,#content div.box div.h4,#content div.box div.h5,#content div.box div.h6
1158 1158
1159 1159 {
1160 1160 clear: both;
1161 1161 overflow: hidden;
1162 1162 border-bottom: 1px solid #DDD;
1163 1163 margin: 10px 20px;
1164 1164 padding: 0 0 15px;
1165 1165 }
1166 1166
1167 1167 #content div.box p {
1168 1168 color: #5f5f5f;
1169 1169 font-size: 12px;
1170 1170 line-height: 150%;
1171 1171 margin: 0 24px 10px;
1172 1172 padding: 0;
1173 1173 }
1174 1174
1175 1175 #content div.box blockquote {
1176 1176 border-left: 4px solid #DDD;
1177 1177 color: #5f5f5f;
1178 1178 font-size: 11px;
1179 1179 line-height: 150%;
1180 1180 margin: 0 34px;
1181 1181 padding: 0 0 0 14px;
1182 1182 }
1183 1183
1184 1184 #content div.box blockquote p {
1185 1185 margin: 10px 0;
1186 1186 padding: 0;
1187 1187 }
1188 1188
1189 1189 #content div.box dl {
1190 1190 margin: 10px 0px;
1191 1191 }
1192 1192
1193 1193 #content div.box dt {
1194 1194 font-size: 12px;
1195 1195 margin: 0;
1196 1196 }
1197 1197
1198 1198 #content div.box dd {
1199 1199 font-size: 12px;
1200 1200 margin: 0;
1201 1201 padding: 8px 0 8px 15px;
1202 1202 }
1203 1203
1204 1204 #content div.box li {
1205 1205 font-size: 12px;
1206 1206 padding: 4px 0;
1207 1207 }
1208 1208
1209 1209 #content div.box ul.disc,#content div.box ul.circle {
1210 1210 margin: 10px 24px 10px 38px;
1211 1211 }
1212 1212
1213 1213 #content div.box ul.square {
1214 1214 margin: 10px 24px 10px 40px;
1215 1215 }
1216 1216
1217 1217 #content div.box img.left {
1218 1218 border: none;
1219 1219 float: left;
1220 1220 margin: 10px 10px 10px 0;
1221 1221 }
1222 1222
1223 1223 #content div.box img.right {
1224 1224 border: none;
1225 1225 float: right;
1226 1226 margin: 10px 0 10px 10px;
1227 1227 }
1228 1228
1229 1229 #content div.box div.messages {
1230 1230 clear: both;
1231 1231 overflow: hidden;
1232 1232 margin: 0 20px;
1233 1233 padding: 0;
1234 1234 }
1235 1235
1236 1236 #content div.box div.message {
1237 1237 clear: both;
1238 1238 overflow: hidden;
1239 1239 margin: 0;
1240 1240 padding: 5px 0;
1241 1241 white-space: pre-wrap;
1242 1242 }
1243 1243 #content div.box div.expand {
1244 1244 width: 110%;
1245 1245 height:14px;
1246 1246 font-size:10px;
1247 1247 text-align:center;
1248 1248 cursor: pointer;
1249 1249 color:#666;
1250 1250
1251 1251 background:-webkit-gradient(linear,0% 50%,100% 50%,color-stop(0%,rgba(255,255,255,0)),color-stop(100%,rgba(64,96,128,0.1)));
1252 1252 background:-webkit-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1253 1253 background:-moz-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1254 1254 background:-o-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1255 1255 background:-ms-linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1256 1256 background:linear-gradient(top,rgba(255,255,255,0),rgba(64,96,128,0.1));
1257 1257
1258 1258 display: none;
1259 1259 }
1260 1260 #content div.box div.expand .expandtext {
1261 1261 background-color: #ffffff;
1262 1262 padding: 2px;
1263 1263 border-radius: 2px;
1264 1264 }
1265 1265
1266 1266 #content div.box div.message a {
1267 1267 font-weight: 400 !important;
1268 1268 }
1269 1269
1270 1270 #content div.box div.message div.image {
1271 1271 float: left;
1272 1272 margin: 9px 0 0 5px;
1273 1273 padding: 6px;
1274 1274 }
1275 1275
1276 1276 #content div.box div.message div.image img {
1277 1277 vertical-align: middle;
1278 1278 margin: 0;
1279 1279 }
1280 1280
1281 1281 #content div.box div.message div.text {
1282 1282 float: left;
1283 1283 margin: 0;
1284 1284 padding: 9px 6px;
1285 1285 }
1286 1286
1287 1287 #content div.box div.message div.dismiss a {
1288 1288 height: 16px;
1289 1289 width: 16px;
1290 1290 display: block;
1291 1291 background: url("../images/icons/cross.png") no-repeat;
1292 1292 margin: 15px 14px 0 0;
1293 1293 padding: 0;
1294 1294 }
1295 1295
1296 1296 #content div.box div.message div.text h1,#content div.box div.message div.text h2,#content div.box div.message div.text h3,#content div.box div.message div.text h4,#content div.box div.message div.text h5,#content div.box div.message div.text h6
1297 1297 {
1298 1298 border: none;
1299 1299 margin: 0;
1300 1300 padding: 0;
1301 1301 }
1302 1302
1303 1303 #content div.box div.message div.text span {
1304 1304 height: 1%;
1305 1305 display: block;
1306 1306 margin: 0;
1307 1307 padding: 5px 0 0;
1308 1308 }
1309 1309
1310 1310 #content div.box div.message-error {
1311 1311 height: 1%;
1312 1312 clear: both;
1313 1313 overflow: hidden;
1314 1314 background: #FBE3E4;
1315 1315 border: 1px solid #FBC2C4;
1316 1316 color: #860006;
1317 1317 }
1318 1318
1319 1319 #content div.box div.message-error h6 {
1320 1320 color: #860006;
1321 1321 }
1322 1322
1323 1323 #content div.box div.message-warning {
1324 1324 height: 1%;
1325 1325 clear: both;
1326 1326 overflow: hidden;
1327 1327 background: #FFF6BF;
1328 1328 border: 1px solid #FFD324;
1329 1329 color: #5f5200;
1330 1330 }
1331 1331
1332 1332 #content div.box div.message-warning h6 {
1333 1333 color: #5f5200;
1334 1334 }
1335 1335
1336 1336 #content div.box div.message-notice {
1337 1337 height: 1%;
1338 1338 clear: both;
1339 1339 overflow: hidden;
1340 1340 background: #8FBDE0;
1341 1341 border: 1px solid #6BACDE;
1342 1342 color: #003863;
1343 1343 }
1344 1344
1345 1345 #content div.box div.message-notice h6 {
1346 1346 color: #003863;
1347 1347 }
1348 1348
1349 1349 #content div.box div.message-success {
1350 1350 height: 1%;
1351 1351 clear: both;
1352 1352 overflow: hidden;
1353 1353 background: #E6EFC2;
1354 1354 border: 1px solid #C6D880;
1355 1355 color: #4e6100;
1356 1356 }
1357 1357
1358 1358 #content div.box div.message-success h6 {
1359 1359 color: #4e6100;
1360 1360 }
1361 1361
1362 1362 #content div.box div.form div.fields div.field {
1363 1363 height: 1%;
1364 1364 border-bottom: 1px solid #DDD;
1365 1365 clear: both;
1366 1366 margin: 0;
1367 1367 padding: 10px 0;
1368 1368 }
1369 1369
1370 1370 #content div.box div.form div.fields div.field-first {
1371 1371 padding: 0 0 10px;
1372 1372 }
1373 1373
1374 1374 #content div.box div.form div.fields div.field-noborder {
1375 1375 border-bottom: 0 !important;
1376 1376 }
1377 1377
1378 1378 #content div.box div.form div.fields div.field span.error-message {
1379 1379 height: 1%;
1380 1380 display: inline-block;
1381 1381 color: red;
1382 1382 margin: 8px 0 0 4px;
1383 1383 padding: 0;
1384 1384 }
1385 1385
1386 1386 #content div.box div.form div.fields div.field span.success {
1387 1387 height: 1%;
1388 1388 display: block;
1389 1389 color: #316309;
1390 1390 margin: 8px 0 0;
1391 1391 padding: 0;
1392 1392 }
1393 1393
1394 1394 #content div.box div.form div.fields div.field div.label {
1395 1395 left: 70px;
1396 1396 width: 155px;
1397 1397 position: absolute;
1398 1398 margin: 0;
1399 1399 padding: 5px 0 0 0px;
1400 1400 }
1401 1401
1402 1402 #content div.box div.form div.fields div.field div.label-summary {
1403 1403 left: 30px;
1404 1404 width: 155px;
1405 1405 position: absolute;
1406 1406 margin: 0;
1407 1407 padding: 0px 0 0 0px;
1408 1408 }
1409 1409
1410 1410 #content div.box-left div.form div.fields div.field div.label,
1411 1411 #content div.box-right div.form div.fields div.field div.label,
1412 1412 #content div.box-left div.form div.fields div.field div.label,
1413 1413 #content div.box-left div.form div.fields div.field div.label-summary,
1414 1414 #content div.box-right div.form div.fields div.field div.label-summary,
1415 1415 #content div.box-left div.form div.fields div.field div.label-summary
1416 1416 {
1417 1417 clear: both;
1418 1418 overflow: hidden;
1419 1419 left: 0;
1420 1420 width: auto;
1421 1421 position: relative;
1422 1422 margin: 0;
1423 1423 padding: 0 0 8px;
1424 1424 }
1425 1425
1426 1426 #content div.box div.form div.fields div.field div.label-select {
1427 1427 padding: 5px 0 0 5px;
1428 1428 }
1429 1429
1430 1430 #content div.box-left div.form div.fields div.field div.label-select,
1431 1431 #content div.box-right div.form div.fields div.field div.label-select
1432 1432 {
1433 1433 padding: 0 0 8px;
1434 1434 }
1435 1435
1436 1436 #content div.box-left div.form div.fields div.field div.label-textarea,
1437 1437 #content div.box-right div.form div.fields div.field div.label-textarea
1438 1438 {
1439 1439 padding: 0 0 8px !important;
1440 1440 }
1441 1441
1442 1442 #content div.box div.form div.fields div.field div.label label,div.label label
1443 1443 {
1444 1444 color: #393939;
1445 1445 font-weight: 700;
1446 1446 }
1447 1447 #content div.box div.form div.fields div.field div.label label,div.label-summary label
1448 1448 {
1449 1449 color: #393939;
1450 1450 font-weight: 700;
1451 1451 }
1452 1452 #content div.box div.form div.fields div.field div.input {
1453 1453 margin: 0 0 0 200px;
1454 1454 }
1455 1455
1456 1456 #content div.box div.form div.fields div.field div.input.summary {
1457 1457 margin: 0 0 0 110px;
1458 1458 }
1459 1459 #content div.box div.form div.fields div.field div.input.summary-short {
1460 1460 margin: 0 0 0 110px;
1461 1461 }
1462 1462 #content div.box div.form div.fields div.field div.file {
1463 1463 margin: 0 0 0 200px;
1464 1464 }
1465 1465
1466 1466 #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input
1467 1467 {
1468 1468 margin: 0 0 0 0px;
1469 1469 }
1470 1470
1471 1471 #content div.box div.form div.fields div.field div.input input,
1472 1472 .reviewer_ac input {
1473 1473 background: #FFF;
1474 1474 border-top: 1px solid #b3b3b3;
1475 1475 border-left: 1px solid #b3b3b3;
1476 1476 border-right: 1px solid #eaeaea;
1477 1477 border-bottom: 1px solid #eaeaea;
1478 1478 color: #000;
1479 1479 font-size: 11px;
1480 1480 margin: 0;
1481 1481 padding: 7px 7px 6px;
1482 1482 }
1483 1483
1484 1484 #content div.box div.form div.fields div.field div.input input#clone_url,
1485 1485 #content div.box div.form div.fields div.field div.input input#clone_url_id
1486 1486 {
1487 1487 font-size: 16px;
1488 1488 padding: 2px;
1489 1489 }
1490 1490
1491 1491 #content div.box div.form div.fields div.field div.file input {
1492 1492 background: none repeat scroll 0 0 #FFFFFF;
1493 1493 border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
1494 1494 border-style: solid;
1495 1495 border-width: 1px;
1496 1496 color: #000000;
1497 1497 font-size: 11px;
1498 1498 margin: 0;
1499 1499 padding: 7px 7px 6px;
1500 1500 }
1501 1501
1502 1502 input.disabled {
1503 1503 background-color: #F5F5F5 !important;
1504 1504 }
1505 1505 #content div.box div.form div.fields div.field div.input input.small {
1506 1506 width: 30%;
1507 1507 }
1508 1508
1509 1509 #content div.box div.form div.fields div.field div.input input.medium {
1510 1510 width: 55%;
1511 1511 }
1512 1512
1513 1513 #content div.box div.form div.fields div.field div.input input.large {
1514 1514 width: 85%;
1515 1515 }
1516 1516
1517 1517 #content div.box div.form div.fields div.field div.input input.date {
1518 1518 width: 177px;
1519 1519 }
1520 1520
1521 1521 #content div.box div.form div.fields div.field div.input input.button {
1522 1522 background: #D4D0C8;
1523 1523 border-top: 1px solid #FFF;
1524 1524 border-left: 1px solid #FFF;
1525 1525 border-right: 1px solid #404040;
1526 1526 border-bottom: 1px solid #404040;
1527 1527 color: #000;
1528 1528 margin: 0;
1529 1529 padding: 4px 8px;
1530 1530 }
1531 1531
1532 1532 #content div.box div.form div.fields div.field div.textarea {
1533 1533 border-top: 1px solid #b3b3b3;
1534 1534 border-left: 1px solid #b3b3b3;
1535 1535 border-right: 1px solid #eaeaea;
1536 1536 border-bottom: 1px solid #eaeaea;
1537 1537 margin: 0 0 0 200px;
1538 1538 padding: 10px;
1539 1539 }
1540 1540
1541 1541 #content div.box div.form div.fields div.field div.textarea-editor {
1542 1542 border: 1px solid #ddd;
1543 1543 padding: 0;
1544 1544 }
1545 1545
1546 1546 #content div.box div.form div.fields div.field div.textarea textarea {
1547 1547 width: 100%;
1548 1548 height: 220px;
1549 1549 overflow: hidden;
1550 1550 background: #FFF;
1551 1551 color: #000;
1552 1552 font-size: 11px;
1553 1553 outline: none;
1554 1554 border-width: 0;
1555 1555 margin: 0;
1556 1556 padding: 0;
1557 1557 }
1558 1558
1559 1559 #content div.box-left div.form div.fields div.field div.textarea textarea,#content div.box-right div.form div.fields div.field div.textarea textarea
1560 1560 {
1561 1561 width: 100%;
1562 1562 height: 100px;
1563 1563 }
1564 1564
1565 1565 #content div.box div.form div.fields div.field div.textarea table {
1566 1566 width: 100%;
1567 1567 border: none;
1568 1568 margin: 0;
1569 1569 padding: 0;
1570 1570 }
1571 1571
1572 1572 #content div.box div.form div.fields div.field div.textarea table td {
1573 1573 background: #DDD;
1574 1574 border: none;
1575 1575 padding: 0;
1576 1576 }
1577 1577
1578 1578 #content div.box div.form div.fields div.field div.textarea table td table
1579 1579 {
1580 1580 width: auto;
1581 1581 border: none;
1582 1582 margin: 0;
1583 1583 padding: 0;
1584 1584 }
1585 1585
1586 1586 #content div.box div.form div.fields div.field div.textarea table td table td
1587 1587 {
1588 1588 font-size: 11px;
1589 1589 padding: 5px 5px 5px 0;
1590 1590 }
1591 1591
1592 1592 #content div.box div.form div.fields div.field input[type=text]:focus,
1593 1593 #content div.box div.form div.fields div.field input[type=password]:focus,
1594 1594 #content div.box div.form div.fields div.field input[type=file]:focus,
1595 1595 #content div.box div.form div.fields div.field textarea:focus,
1596 1596 #content div.box div.form div.fields div.field select:focus,
1597 1597 .reviewer_ac input:focus
1598 1598 {
1599 1599 background: #f6f6f6;
1600 1600 border-color: #666;
1601 1601 }
1602 1602
1603 1603 .reviewer_ac {
1604 1604 padding:10px
1605 1605 }
1606 1606
1607 1607 div.form div.fields div.field div.button {
1608 1608 margin: 0;
1609 1609 padding: 0 0 0 8px;
1610 1610 }
1611 1611 #content div.box table.noborder {
1612 1612 border: 1px solid transparent;
1613 1613 }
1614 1614
1615 1615 #content div.box table {
1616 1616 width: 100%;
1617 1617 border-collapse: separate;
1618 1618 margin: 0;
1619 1619 padding: 0;
1620 1620 border: 1px solid #eee;
1621 1621 -webkit-border-radius: 4px;
1622 1622 -moz-border-radius: 4px;
1623 1623 border-radius: 4px;
1624 1624 }
1625 1625
1626 1626 #content div.box table th {
1627 1627 background: #eee;
1628 1628 border-bottom: 1px solid #ddd;
1629 1629 padding: 5px 0px 5px 5px;
1630 1630 }
1631 1631
1632 1632 #content div.box table th.left {
1633 1633 text-align: left;
1634 1634 }
1635 1635
1636 1636 #content div.box table th.right {
1637 1637 text-align: right;
1638 1638 }
1639 1639
1640 1640 #content div.box table th.center {
1641 1641 text-align: center;
1642 1642 }
1643 1643
1644 1644 #content div.box table th.selected {
1645 1645 vertical-align: middle;
1646 1646 padding: 0;
1647 1647 }
1648 1648
1649 1649 #content div.box table td {
1650 1650 background: #fff;
1651 1651 border-bottom: 1px solid #cdcdcd;
1652 1652 vertical-align: middle;
1653 1653 padding: 5px;
1654 1654 }
1655 1655
1656 1656 #content div.box table tr.selected td {
1657 1657 background: #FFC;
1658 1658 }
1659 1659
1660 1660 #content div.box table td.selected {
1661 1661 width: 3%;
1662 1662 text-align: center;
1663 1663 vertical-align: middle;
1664 1664 padding: 0;
1665 1665 }
1666 1666
1667 1667 #content div.box table td.action {
1668 1668 width: 45%;
1669 1669 text-align: left;
1670 1670 }
1671 1671
1672 1672 #content div.box table td.date {
1673 1673 width: 33%;
1674 1674 text-align: center;
1675 1675 }
1676 1676
1677 1677 #content div.box div.action {
1678 1678 float: right;
1679 1679 background: #FFF;
1680 1680 text-align: right;
1681 1681 margin: 10px 0 0;
1682 1682 padding: 0;
1683 1683 }
1684 1684
1685 1685 #content div.box div.action select {
1686 1686 font-size: 11px;
1687 1687 margin: 0;
1688 1688 }
1689 1689
1690 1690 #content div.box div.action .ui-selectmenu {
1691 1691 margin: 0;
1692 1692 padding: 0;
1693 1693 }
1694 1694
1695 1695 #content div.box div.pagination {
1696 1696 height: 1%;
1697 1697 clear: both;
1698 1698 overflow: hidden;
1699 1699 margin: 10px 0 0;
1700 1700 padding: 0;
1701 1701 }
1702 1702
1703 1703 #content div.box div.pagination ul.pager {
1704 1704 float: right;
1705 1705 text-align: right;
1706 1706 margin: 0;
1707 1707 padding: 0;
1708 1708 }
1709 1709
1710 1710 #content div.box div.pagination ul.pager li {
1711 1711 height: 1%;
1712 1712 float: left;
1713 1713 list-style: none;
1714 1714 background: #ebebeb url("../images/pager.png") repeat-x;
1715 1715 border-top: 1px solid #dedede;
1716 1716 border-left: 1px solid #cfcfcf;
1717 1717 border-right: 1px solid #c4c4c4;
1718 1718 border-bottom: 1px solid #c4c4c4;
1719 1719 color: #4A4A4A;
1720 1720 font-weight: 700;
1721 1721 margin: 0 0 0 4px;
1722 1722 padding: 0;
1723 1723 }
1724 1724
1725 1725 #content div.box div.pagination ul.pager li.separator {
1726 1726 padding: 6px;
1727 1727 }
1728 1728
1729 1729 #content div.box div.pagination ul.pager li.current {
1730 1730 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1731 1731 border-top: 1px solid #ccc;
1732 1732 border-left: 1px solid #bebebe;
1733 1733 border-right: 1px solid #b1b1b1;
1734 1734 border-bottom: 1px solid #afafaf;
1735 1735 color: #515151;
1736 1736 padding: 6px;
1737 1737 }
1738 1738
1739 1739 #content div.box div.pagination ul.pager li a {
1740 1740 height: 1%;
1741 1741 display: block;
1742 1742 float: left;
1743 1743 color: #515151;
1744 1744 text-decoration: none;
1745 1745 margin: 0;
1746 1746 padding: 6px;
1747 1747 }
1748 1748
1749 1749 #content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active
1750 1750 {
1751 1751 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1752 1752 border-top: 1px solid #ccc;
1753 1753 border-left: 1px solid #bebebe;
1754 1754 border-right: 1px solid #b1b1b1;
1755 1755 border-bottom: 1px solid #afafaf;
1756 1756 margin: -1px;
1757 1757 }
1758 1758
1759 1759 #content div.box div.pagination-wh {
1760 1760 height: 1%;
1761 1761 clear: both;
1762 1762 overflow: hidden;
1763 1763 text-align: right;
1764 1764 margin: 10px 0 0;
1765 1765 padding: 0;
1766 1766 }
1767 1767
1768 1768 #content div.box div.pagination-right {
1769 1769 float: right;
1770 1770 }
1771 1771
1772 1772 #content div.box div.pagination-wh a,
1773 1773 #content div.box div.pagination-wh span.pager_dotdot,
1774 1774 #content div.box div.pagination-wh span.yui-pg-previous,
1775 1775 #content div.box div.pagination-wh span.yui-pg-last,
1776 1776 #content div.box div.pagination-wh span.yui-pg-next,
1777 1777 #content div.box div.pagination-wh span.yui-pg-first
1778 1778 {
1779 1779 height: 1%;
1780 1780 float: left;
1781 1781 background: #ebebeb url("../images/pager.png") repeat-x;
1782 1782 border-top: 1px solid #dedede;
1783 1783 border-left: 1px solid #cfcfcf;
1784 1784 border-right: 1px solid #c4c4c4;
1785 1785 border-bottom: 1px solid #c4c4c4;
1786 1786 color: #4A4A4A;
1787 1787 font-weight: 700;
1788 1788 margin: 0 0 0 4px;
1789 1789 padding: 6px;
1790 1790 }
1791 1791
1792 1792 #content div.box div.pagination-wh span.pager_curpage {
1793 1793 height: 1%;
1794 1794 float: left;
1795 1795 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1796 1796 border-top: 1px solid #ccc;
1797 1797 border-left: 1px solid #bebebe;
1798 1798 border-right: 1px solid #b1b1b1;
1799 1799 border-bottom: 1px solid #afafaf;
1800 1800 color: #515151;
1801 1801 font-weight: 700;
1802 1802 margin: 0 0 0 4px;
1803 1803 padding: 6px;
1804 1804 }
1805 1805
1806 1806 #content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active
1807 1807 {
1808 1808 background: #b4b4b4 url("../images/pager_selected.png") repeat-x;
1809 1809 border-top: 1px solid #ccc;
1810 1810 border-left: 1px solid #bebebe;
1811 1811 border-right: 1px solid #b1b1b1;
1812 1812 border-bottom: 1px solid #afafaf;
1813 1813 text-decoration: none;
1814 1814 }
1815 1815
1816 1816 #content div.box div.traffic div.legend {
1817 1817 clear: both;
1818 1818 overflow: hidden;
1819 1819 border-bottom: 1px solid #ddd;
1820 1820 margin: 0 0 10px;
1821 1821 padding: 0 0 10px;
1822 1822 }
1823 1823
1824 1824 #content div.box div.traffic div.legend h6 {
1825 1825 float: left;
1826 1826 border: none;
1827 1827 margin: 0;
1828 1828 padding: 0;
1829 1829 }
1830 1830
1831 1831 #content div.box div.traffic div.legend li {
1832 1832 list-style: none;
1833 1833 float: left;
1834 1834 font-size: 11px;
1835 1835 margin: 0;
1836 1836 padding: 0 8px 0 4px;
1837 1837 }
1838 1838
1839 1839 #content div.box div.traffic div.legend li.visits {
1840 1840 border-left: 12px solid #edc240;
1841 1841 }
1842 1842
1843 1843 #content div.box div.traffic div.legend li.pageviews {
1844 1844 border-left: 12px solid #afd8f8;
1845 1845 }
1846 1846
1847 1847 #content div.box div.traffic table {
1848 1848 width: auto;
1849 1849 }
1850 1850
1851 1851 #content div.box div.traffic table td {
1852 1852 background: transparent;
1853 1853 border: none;
1854 1854 padding: 2px 3px 3px;
1855 1855 }
1856 1856
1857 1857 #content div.box div.traffic table td.legendLabel {
1858 1858 padding: 0 3px 2px;
1859 1859 }
1860 1860
1861 1861 #summary {
1862 1862
1863 1863 }
1864 1864
1865 1865 #summary .metatag {
1866 1866 display: inline-block;
1867 1867 padding: 3px 5px;
1868 1868 margin-bottom: 3px;
1869 1869 margin-right: 1px;
1870 1870 border-radius: 5px;
1871 1871 }
1872 1872
1873 1873 #content div.box #summary p {
1874 1874 margin-bottom: -5px;
1875 1875 width: 600px;
1876 1876 white-space: pre-wrap;
1877 1877 }
1878 1878
1879 1879 #content div.box #summary p:last-child {
1880 1880 margin-bottom: 9px;
1881 1881 }
1882 1882
1883 1883 #content div.box #summary p:first-of-type {
1884 1884 margin-top: 9px;
1885 1885 }
1886 1886
1887 1887 .metatag {
1888 1888 display: inline-block;
1889 1889 margin-right: 1px;
1890 1890 -webkit-border-radius: 4px 4px 4px 4px;
1891 1891 -khtml-border-radius: 4px 4px 4px 4px;
1892 1892 -moz-border-radius: 4px 4px 4px 4px;
1893 1893 border-radius: 4px 4px 4px 4px;
1894 1894
1895 1895 border: solid 1px #9CF;
1896 1896 padding: 2px 3px 2px 3px !important;
1897 1897 background-color: #DEF;
1898 1898 }
1899 1899
1900 1900 .metatag[tag="dead"] {
1901 1901 background-color: #E44;
1902 1902 }
1903 1903
1904 1904 .metatag[tag="stale"] {
1905 1905 background-color: #EA4;
1906 1906 }
1907 1907
1908 1908 .metatag[tag="featured"] {
1909 1909 background-color: #AEA;
1910 1910 }
1911 1911
1912 1912 .metatag[tag="requires"] {
1913 1913 background-color: #9CF;
1914 1914 }
1915 1915
1916 1916 .metatag[tag="recommends"] {
1917 1917 background-color: #BDF;
1918 1918 }
1919 1919
1920 1920 .metatag[tag="lang"] {
1921 1921 background-color: #FAF474;
1922 1922 }
1923 1923
1924 1924 .metatag[tag="license"] {
1925 1925 border: solid 1px #9CF;
1926 1926 background-color: #DEF;
1927 1927 target-new: tab !important;
1928 1928 }
1929 1929 .metatag[tag="see"] {
1930 1930 border: solid 1px #CBD;
1931 1931 background-color: #EDF;
1932 1932 }
1933 1933
1934 1934 a.metatag[tag="license"]:hover {
1935 1935 background-color: #003367;
1936 1936 color: #FFF;
1937 1937 text-decoration: none;
1938 1938 }
1939 1939
1940 1940 #summary .desc {
1941 1941 white-space: pre;
1942 1942 width: 100%;
1943 1943 }
1944 1944
1945 1945 #summary .repo_name {
1946 1946 font-size: 1.6em;
1947 1947 font-weight: bold;
1948 1948 vertical-align: baseline;
1949 1949 clear: right
1950 1950 }
1951 1951
1952 1952 #footer {
1953 1953 clear: both;
1954 1954 overflow: hidden;
1955 1955 text-align: right;
1956 1956 margin: 0;
1957 1957 padding: 0 10px 4px;
1958 1958 margin: -10px 0 0;
1959 1959 }
1960 1960
1961 1961 #footer div#footer-inner {
1962 1962 background-color: #003B76;
1963 1963 background-repeat : repeat-x;
1964 1964 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
1965 1965 background-image : -moz-linear-gradient(top, #003b76, #00376e);
1966 1966 background-image : -ms-linear-gradient( top, #003b76, #00376e);
1967 1967 background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
1968 1968 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
1969 1969 background-image : -o-linear-gradient( top, #003b76, #00376e));
1970 1970 background-image : linear-gradient( top, #003b76, #00376e);
1971 1971 filter :progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
1972 1972 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1973 1973 -webkit-border-radius: 4px 4px 4px 4px;
1974 1974 -khtml-border-radius: 4px 4px 4px 4px;
1975 1975 -moz-border-radius: 4px 4px 4px 4px;
1976 1976 border-radius: 4px 4px 4px 4px;
1977 1977 }
1978 1978
1979 1979 #footer div#footer-inner p {
1980 1980 padding: 15px 25px 15px 0;
1981 1981 color: #FFF;
1982 1982 font-weight: 700;
1983 1983 }
1984 1984
1985 1985 #footer div#footer-inner .footer-link {
1986 1986 float: left;
1987 1987 padding-left: 10px;
1988 1988 }
1989 1989
1990 1990 #footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a
1991 1991 {
1992 1992 color: #FFF;
1993 1993 }
1994 1994
1995 1995 #login div.title {
1996 1996 width: 420px;
1997 1997 clear: both;
1998 1998 overflow: hidden;
1999 1999 position: relative;
2000 2000 background-color: #003B76;
2001 2001 background-repeat : repeat-x;
2002 2002 background-image : -khtml-gradient( linear, left top, left bottom, from(#003B76), to(#00376E));
2003 2003 background-image : -moz-linear-gradient( top, #003b76, #00376e);
2004 2004 background-image : -ms-linear-gradient( top, #003b76, #00376e);
2005 2005 background-image : -webkit-gradient( linear, left top, left bottom, color-stop( 0%, #003b76), color-stop( 100%, #00376e));
2006 2006 background-image : -webkit-linear-gradient( top, #003b76, #00376e));
2007 2007 background-image : -o-linear-gradient( top, #003b76, #00376e));
2008 2008 background-image : linear-gradient( top, #003b76, #00376e);
2009 2009 filter : progid : DXImageTransform.Microsoft.gradient ( startColorstr = '#003b76', endColorstr = '#00376e', GradientType = 0);
2010 2010 margin: 0 auto;
2011 2011 padding: 0;
2012 2012 }
2013 2013
2014 2014 #login div.inner {
2015 2015 width: 380px;
2016 2016 background: #FFF url("../images/login.png") no-repeat top left;
2017 2017 border-top: none;
2018 2018 border-bottom: none;
2019 2019 margin: 0 auto;
2020 2020 padding: 20px;
2021 2021 }
2022 2022
2023 2023 #login div.form div.fields div.field div.label {
2024 2024 width: 173px;
2025 2025 float: left;
2026 2026 text-align: right;
2027 2027 margin: 2px 10px 0 0;
2028 2028 padding: 5px 0 0 5px;
2029 2029 }
2030 2030
2031 2031 #login div.form div.fields div.field div.input input {
2032 2032 width: 176px;
2033 2033 background: #FFF;
2034 2034 border-top: 1px solid #b3b3b3;
2035 2035 border-left: 1px solid #b3b3b3;
2036 2036 border-right: 1px solid #eaeaea;
2037 2037 border-bottom: 1px solid #eaeaea;
2038 2038 color: #000;
2039 2039 font-size: 11px;
2040 2040 margin: 0;
2041 2041 padding: 7px 7px 6px;
2042 2042 }
2043 2043
2044 2044 #login div.form div.fields div.buttons {
2045 2045 clear: both;
2046 2046 overflow: hidden;
2047 2047 border-top: 1px solid #DDD;
2048 2048 text-align: right;
2049 2049 margin: 0;
2050 2050 padding: 10px 0 0;
2051 2051 }
2052 2052
2053 2053 #login div.form div.links {
2054 2054 clear: both;
2055 2055 overflow: hidden;
2056 2056 margin: 10px 0 0;
2057 2057 padding: 0 0 2px;
2058 2058 }
2059 2059
2060 2060 .user-menu{
2061 2061 margin: 0px !important;
2062 2062 float: left;
2063 2063 }
2064 2064
2065 2065 .user-menu .container{
2066 2066 padding:0px 4px 0px 4px;
2067 2067 margin: 0px 0px 0px 0px;
2068 2068 }
2069 2069
2070 2070 .user-menu .gravatar{
2071 2071 margin: 0px 0px 0px 0px;
2072 2072 cursor: pointer;
2073 2073 }
2074 2074 .user-menu .gravatar.enabled{
2075 2075 background-color: #FDF784 !important;
2076 2076 }
2077 2077 .user-menu .gravatar:hover{
2078 2078 background-color: #FDF784 !important;
2079 2079 }
2080 2080 #quick_login{
2081 2081 min-height: 80px;
2082 2082 margin: 37px 0 0 -251px;
2083 2083 padding: 4px;
2084 2084 position: absolute;
2085 2085 width: 278px;
2086 2086 background-color: #003B76;
2087 2087 background-repeat: repeat-x;
2088 2088 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
2089 2089 background-image: -moz-linear-gradient(top, #003b76, #00376e);
2090 2090 background-image: -ms-linear-gradient(top, #003b76, #00376e);
2091 2091 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
2092 2092 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
2093 2093 background-image: -o-linear-gradient(top, #003b76, #00376e);
2094 2094 background-image: linear-gradient(top, #003b76, #00376e);
2095 2095 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76', endColorstr='#00376e', GradientType=0 );
2096 2096
2097 2097 z-index: 999;
2098 2098 -webkit-border-radius: 0px 0px 4px 4px;
2099 2099 -khtml-border-radius: 0px 0px 4px 4px;
2100 2100 -moz-border-radius: 0px 0px 4px 4px;
2101 2101 border-radius: 0px 0px 4px 4px;
2102 2102 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
2103 2103 }
2104 2104 #quick_login h4{
2105 2105 color: #fff;
2106 2106 padding: 5px 0px 5px 14px;
2107 2107 }
2108 2108
2109 2109 #quick_login .password_forgoten {
2110 2110 padding-right: 10px;
2111 2111 padding-top: 0px;
2112 2112 text-align: left;
2113 2113 }
2114 2114
2115 2115 #quick_login .password_forgoten a {
2116 2116 font-size: 10px;
2117 2117 color: #fff;
2118 2118 }
2119 2119
2120 2120 #quick_login .register {
2121 2121 padding-right: 10px;
2122 2122 padding-top: 5px;
2123 2123 text-align: left;
2124 2124 }
2125 2125
2126 2126 #quick_login .register a {
2127 2127 font-size: 10px;
2128 2128 color: #fff;
2129 2129 }
2130 2130
2131 2131 #quick_login .submit {
2132 2132 margin: -20px 0 0 0px;
2133 2133 position: absolute;
2134 2134 right: 15px;
2135 2135 }
2136 2136
2137 2137 #quick_login .links_left{
2138 2138 float: left;
2139 2139 }
2140 2140 #quick_login .links_right{
2141 2141 float: right;
2142 2142 }
2143 2143 #quick_login .full_name{
2144 2144 color: #FFFFFF;
2145 2145 font-weight: bold;
2146 2146 padding: 3px;
2147 2147 }
2148 2148 #quick_login .big_gravatar{
2149 2149 padding:4px 0px 0px 6px;
2150 2150 }
2151 2151 #quick_login .inbox{
2152 2152 padding:4px 0px 0px 6px;
2153 2153 color: #FFFFFF;
2154 2154 font-weight: bold;
2155 2155 }
2156 2156 #quick_login .inbox a{
2157 2157 color: #FFFFFF;
2158 2158 }
2159 2159 #quick_login .email,#quick_login .email a{
2160 2160 color: #FFFFFF;
2161 2161 padding: 3px;
2162 2162
2163 2163 }
2164 2164 #quick_login .links .logout{
2165 2165
2166 2166 }
2167 2167
2168 2168 #quick_login div.form div.fields {
2169 2169 padding-top: 2px;
2170 2170 padding-left: 10px;
2171 2171 }
2172 2172
2173 2173 #quick_login div.form div.fields div.field {
2174 2174 padding: 5px;
2175 2175 }
2176 2176
2177 2177 #quick_login div.form div.fields div.field div.label label {
2178 2178 color: #fff;
2179 2179 padding-bottom: 3px;
2180 2180 }
2181 2181
2182 2182 #quick_login div.form div.fields div.field div.input input {
2183 2183 width: 236px;
2184 2184 background: #FFF;
2185 2185 border-top: 1px solid #b3b3b3;
2186 2186 border-left: 1px solid #b3b3b3;
2187 2187 border-right: 1px solid #eaeaea;
2188 2188 border-bottom: 1px solid #eaeaea;
2189 2189 color: #000;
2190 2190 font-size: 11px;
2191 2191 margin: 0;
2192 2192 padding: 5px 7px 4px;
2193 2193 }
2194 2194
2195 2195 #quick_login div.form div.fields div.buttons {
2196 2196 clear: both;
2197 2197 overflow: hidden;
2198 2198 text-align: right;
2199 2199 margin: 0;
2200 2200 padding: 5px 14px 0px 5px;
2201 2201 }
2202 2202
2203 2203 #quick_login div.form div.links {
2204 2204 clear: both;
2205 2205 overflow: hidden;
2206 2206 margin: 10px 0 0;
2207 2207 padding: 0 0 2px;
2208 2208 }
2209 2209
2210 2210 #quick_login ol.links{
2211 2211 display: block;
2212 2212 font-weight: bold;
2213 2213 list-style: none outside none;
2214 2214 text-align: right;
2215 2215 }
2216 2216 #quick_login ol.links li{
2217 2217 line-height: 27px;
2218 2218 margin: 0;
2219 2219 padding: 0;
2220 2220 color: #fff;
2221 2221 display: block;
2222 2222 float:none !important;
2223 2223 }
2224 2224
2225 2225 #quick_login ol.links li a{
2226 2226 color: #fff;
2227 2227 display: block;
2228 2228 padding: 2px;
2229 2229 }
2230 2230 #quick_login ol.links li a:HOVER{
2231 2231 background-color: inherit !important;
2232 2232 }
2233 2233
2234 2234 #register div.title {
2235 2235 clear: both;
2236 2236 overflow: hidden;
2237 2237 position: relative;
2238 2238 background-color: #003B76;
2239 2239 background-repeat: repeat-x;
2240 2240 background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) );
2241 2241 background-image: -moz-linear-gradient(top, #003b76, #00376e);
2242 2242 background-image: -ms-linear-gradient(top, #003b76, #00376e);
2243 2243 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
2244 2244 background-image: -webkit-linear-gradient(top, #003b76, #00376e);
2245 2245 background-image: -o-linear-gradient(top, #003b76, #00376e);
2246 2246 background-image: linear-gradient(top, #003b76, #00376e);
2247 2247 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
2248 2248 endColorstr='#00376e', GradientType=0 );
2249 2249 margin: 0 auto;
2250 2250 padding: 0;
2251 2251 }
2252 2252
2253 2253 #register div.inner {
2254 2254 background: #FFF;
2255 2255 border-top: none;
2256 2256 border-bottom: none;
2257 2257 margin: 0 auto;
2258 2258 padding: 20px;
2259 2259 }
2260 2260
2261 2261 #register div.form div.fields div.field div.label {
2262 2262 width: 135px;
2263 2263 float: left;
2264 2264 text-align: right;
2265 2265 margin: 2px 10px 0 0;
2266 2266 padding: 5px 0 0 5px;
2267 2267 }
2268 2268
2269 2269 #register div.form div.fields div.field div.input input {
2270 2270 width: 300px;
2271 2271 background: #FFF;
2272 2272 border-top: 1px solid #b3b3b3;
2273 2273 border-left: 1px solid #b3b3b3;
2274 2274 border-right: 1px solid #eaeaea;
2275 2275 border-bottom: 1px solid #eaeaea;
2276 2276 color: #000;
2277 2277 font-size: 11px;
2278 2278 margin: 0;
2279 2279 padding: 7px 7px 6px;
2280 2280 }
2281 2281
2282 2282 #register div.form div.fields div.buttons {
2283 2283 clear: both;
2284 2284 overflow: hidden;
2285 2285 border-top: 1px solid #DDD;
2286 2286 text-align: left;
2287 2287 margin: 0;
2288 2288 padding: 10px 0 0 150px;
2289 2289 }
2290 2290
2291 2291 #register div.form div.activation_msg {
2292 2292 padding-top: 4px;
2293 2293 padding-bottom: 4px;
2294 2294 }
2295 2295
2296 2296 #journal .journal_day {
2297 2297 font-size: 20px;
2298 2298 padding: 10px 0px;
2299 2299 border-bottom: 2px solid #DDD;
2300 2300 margin-left: 10px;
2301 2301 margin-right: 10px;
2302 2302 }
2303 2303
2304 2304 #journal .journal_container {
2305 2305 padding: 5px;
2306 2306 clear: both;
2307 2307 margin: 0px 5px 0px 10px;
2308 2308 }
2309 2309
2310 2310 #journal .journal_action_container {
2311 2311 padding-left: 38px;
2312 2312 }
2313 2313
2314 2314 #journal .journal_user {
2315 2315 color: #747474;
2316 2316 font-size: 14px;
2317 2317 font-weight: bold;
2318 2318 height: 30px;
2319 2319 }
2320 2320
2321 2321 #journal .journal_icon {
2322 2322 clear: both;
2323 2323 float: left;
2324 2324 padding-right: 4px;
2325 2325 padding-top: 3px;
2326 2326 }
2327 2327
2328 2328 #journal .journal_action {
2329 2329 padding-top: 4px;
2330 2330 min-height: 2px;
2331 2331 float: left
2332 2332 }
2333 2333
2334 2334 #journal .journal_action_params {
2335 2335 clear: left;
2336 2336 padding-left: 22px;
2337 2337 }
2338 2338
2339 2339 #journal .journal_repo {
2340 2340 float: left;
2341 2341 margin-left: 6px;
2342 2342 padding-top: 3px;
2343 2343 }
2344 2344
2345 2345 #journal .date {
2346 2346 clear: both;
2347 2347 color: #777777;
2348 2348 font-size: 11px;
2349 2349 padding-left: 22px;
2350 2350 }
2351 2351
2352 2352 #journal .journal_repo .journal_repo_name {
2353 2353 font-weight: bold;
2354 2354 font-size: 1.1em;
2355 2355 }
2356 2356
2357 2357 #journal .compare_view {
2358 2358 padding: 5px 0px 5px 0px;
2359 2359 width: 95px;
2360 2360 }
2361 2361
2362 2362 .journal_highlight {
2363 2363 font-weight: bold;
2364 2364 padding: 0 2px;
2365 2365 vertical-align: bottom;
2366 2366 }
2367 2367
2368 2368 .trending_language_tbl,.trending_language_tbl td {
2369 2369 border: 0 !important;
2370 2370 margin: 0 !important;
2371 2371 padding: 0 !important;
2372 2372 }
2373 2373
2374 2374 .trending_language_tbl,.trending_language_tbl tr {
2375 2375 border-spacing: 1px;
2376 2376 }
2377 2377
2378 2378 .trending_language {
2379 2379 background-color: #003367;
2380 2380 color: #FFF;
2381 2381 display: block;
2382 2382 min-width: 20px;
2383 2383 text-decoration: none;
2384 2384 height: 12px;
2385 2385 margin-bottom: 0px;
2386 2386 margin-left: 5px;
2387 2387 white-space: pre;
2388 2388 padding: 3px;
2389 2389 }
2390 2390
2391 2391 h3.files_location {
2392 2392 font-size: 1.8em;
2393 2393 font-weight: 700;
2394 2394 border-bottom: none !important;
2395 2395 margin: 10px 0 !important;
2396 2396 }
2397 2397
2398 2398 #files_data dl dt {
2399 2399 float: left;
2400 2400 width: 60px;
2401 2401 margin: 0 !important;
2402 2402 padding: 5px;
2403 2403 }
2404 2404
2405 2405 #files_data dl dd {
2406 2406 margin: 0 !important;
2407 2407 padding: 5px !important;
2408 2408 }
2409 2409
2410 2410 .file_history{
2411 2411 padding-top:10px;
2412 2412 font-size:16px;
2413 2413 }
2414 2414 .file_author{
2415 2415 float: left;
2416 2416 }
2417 2417
2418 2418 .file_author .item{
2419 2419 float:left;
2420 2420 padding:5px;
2421 2421 color: #888;
2422 2422 }
2423 2423
2424 2424 .tablerow0 {
2425 2425 background-color: #F8F8F8;
2426 2426 }
2427 2427
2428 2428 .tablerow1 {
2429 2429 background-color: #FFFFFF;
2430 2430 }
2431 2431
2432 2432 .changeset_id {
2433 2433 font-family: monospace;
2434 2434 color: #666666;
2435 2435 }
2436 2436
2437 2437 .changeset_hash {
2438 2438 color: #000000;
2439 2439 }
2440 2440
2441 2441 #changeset_content {
2442 2442 border-left: 1px solid #CCC;
2443 2443 border-right: 1px solid #CCC;
2444 2444 border-bottom: 1px solid #CCC;
2445 2445 padding: 5px;
2446 2446 }
2447 2447
2448 2448 #changeset_compare_view_content {
2449 2449 border: 1px solid #CCC;
2450 2450 padding: 5px;
2451 2451 }
2452 2452
2453 2453 #changeset_content .container {
2454 2454 min-height: 100px;
2455 2455 font-size: 1.2em;
2456 2456 overflow: hidden;
2457 2457 }
2458 2458
2459 2459 #changeset_compare_view_content .compare_view_commits {
2460 2460 width: auto !important;
2461 2461 }
2462 2462
2463 2463 #changeset_compare_view_content .compare_view_commits td {
2464 2464 padding: 0px 0px 0px 12px !important;
2465 2465 }
2466 2466
2467 2467 #changeset_content .container .right {
2468 2468 float: right;
2469 2469 width: 20%;
2470 2470 text-align: right;
2471 2471 }
2472 2472
2473 2473 #changeset_content .container .left .message {
2474 2474 white-space: pre-wrap;
2475 2475 }
2476 2476 #changeset_content .container .left .message a:hover {
2477 2477 text-decoration: none;
2478 2478 }
2479 2479 .cs_files .cur_cs {
2480 2480 margin: 10px 2px;
2481 2481 font-weight: bold;
2482 2482 }
2483 2483
2484 2484 .cs_files .node {
2485 2485 float: left;
2486 2486 }
2487 2487
2488 2488 .cs_files .changes {
2489 2489 float: right;
2490 2490 color:#003367;
2491 2491
2492 2492 }
2493 2493
2494 2494 .cs_files .changes .added {
2495 2495 background-color: #BBFFBB;
2496 2496 float: left;
2497 2497 text-align: center;
2498 2498 font-size: 9px;
2499 2499 padding: 2px 0px 2px 0px;
2500 2500 }
2501 2501
2502 2502 .cs_files .changes .deleted {
2503 2503 background-color: #FF8888;
2504 2504 float: left;
2505 2505 text-align: center;
2506 2506 font-size: 9px;
2507 2507 padding: 2px 0px 2px 0px;
2508 2508 }
2509 /*new binary*/
2510 .cs_files .changes .bin1 {
2511 background-color: #BBFFBB;
2512 float: left;
2513 text-align: center;
2514 font-size: 9px;
2515 padding: 2px 0px 2px 0px;
2516 }
2517
2518 /*deleted binary*/
2519 .cs_files .changes .bin2 {
2520 background-color: #FF8888;
2521 float: left;
2522 text-align: center;
2523 font-size: 9px;
2524 padding: 2px 0px 2px 0px;
2525 }
2526
2527 /*mod binary*/
2528 .cs_files .changes .bin3 {
2529 background-color: #DDDDDD;
2530 float: left;
2531 text-align: center;
2532 font-size: 9px;
2533 padding: 2px 0px 2px 0px;
2534 }
2535
2536 /*rename file*/
2537 .cs_files .changes .bin4 {
2538 background-color: #6D99FF;
2539 float: left;
2540 text-align: center;
2541 font-size: 9px;
2542 padding: 2px 0px 2px 0px;
2543 }
2544
2509 2545
2510 2546 .cs_files .cs_added,.cs_files .cs_A {
2511 2547 background: url("../images/icons/page_white_add.png") no-repeat scroll
2512 2548 3px;
2513 2549 height: 16px;
2514 2550 padding-left: 20px;
2515 2551 margin-top: 7px;
2516 2552 text-align: left;
2517 2553 }
2518 2554
2519 2555 .cs_files .cs_changed,.cs_files .cs_M {
2520 2556 background: url("../images/icons/page_white_edit.png") no-repeat scroll
2521 2557 3px;
2522 2558 height: 16px;
2523 2559 padding-left: 20px;
2524 2560 margin-top: 7px;
2525 2561 text-align: left;
2526 2562 }
2527 2563
2528 2564 .cs_files .cs_removed,.cs_files .cs_D {
2529 2565 background: url("../images/icons/page_white_delete.png") no-repeat
2530 2566 scroll 3px;
2531 2567 height: 16px;
2532 2568 padding-left: 20px;
2533 2569 margin-top: 7px;
2534 2570 text-align: left;
2535 2571 }
2536 2572
2537 2573 #graph {
2538 2574 overflow: hidden;
2539 2575 }
2540 2576
2541 2577 #graph_nodes {
2542 2578 float: left;
2543 2579 margin-right: 0px;
2544 2580 margin-top: 0px;
2545 2581 }
2546 2582
2547 2583 #graph_content {
2548 2584 width: 80%;
2549 2585 float: left;
2550 2586 }
2551 2587
2552 2588 #graph_content .container_header {
2553 2589 border-bottom: 1px solid #DDD;
2554 2590 padding: 10px;
2555 2591 height: 25px;
2556 2592 }
2557 2593
2558 2594 #graph_content #rev_range_container {
2559 2595 float: left;
2560 2596 margin: 0px 0px 0px 3px;
2561 2597 }
2562 2598
2563 2599 #graph_content #rev_range_clear {
2564 2600 float: left;
2565 2601 margin: 0px 0px 0px 3px;
2566 2602 }
2567 2603
2568 2604 #graph_content .container {
2569 2605 border-bottom: 1px solid #DDD;
2570 2606 height: 56px;
2571 2607 overflow: hidden;
2572 2608 }
2573 2609
2574 2610 #graph_content .container .right {
2575 2611 float: right;
2576 2612 width: 23%;
2577 2613 text-align: right;
2578 2614 }
2579 2615
2580 2616 #graph_content .container .left {
2581 2617 float: left;
2582 2618 width: 25%;
2583 2619 padding-left: 5px;
2584 2620 }
2585 2621
2586 2622 #graph_content .container .mid {
2587 2623 float: left;
2588 2624 width: 49%;
2589 2625 }
2590 2626
2591 2627
2592 2628 #graph_content .container .left .date {
2593 2629 color: #666;
2594 2630 padding-left: 22px;
2595 2631 font-size: 10px;
2596 2632 }
2597 2633
2598 2634 #graph_content .container .left .author {
2599 2635 height: 22px;
2600 2636 }
2601 2637
2602 2638 #graph_content .container .left .author .user {
2603 2639 color: #444444;
2604 2640 float: left;
2605 2641 margin-left: -4px;
2606 2642 margin-top: 4px;
2607 2643 }
2608 2644
2609 2645 #graph_content .container .mid .message {
2610 2646 white-space: pre-wrap;
2611 2647 }
2612 2648
2613 2649 #graph_content .container .mid .message a:hover{
2614 2650 text-decoration: none;
2615 2651 }
2616 2652 #content #graph_content .message .revision-link,
2617 2653 #changeset_content .container .message .revision-link
2618 2654 {
2619 2655 color:#3F6F9F;
2620 2656 font-weight: bold !important;
2621 2657 }
2622 2658
2623 2659 #content #graph_content .message .issue-tracker-link,
2624 2660 #changeset_content .container .message .issue-tracker-link{
2625 2661 color:#3F6F9F;
2626 2662 font-weight: bold !important;
2627 2663 }
2628 2664
2629 2665 .changeset-status-container{
2630 2666 padding-right: 5px;
2631 2667 margin-top:1px;
2632 2668 float:right;
2633 2669 height:14px;
2634 2670 }
2635 2671 .code-header .changeset-status-container{
2636 2672 float:left;
2637 2673 padding:2px 0px 0px 2px;
2638 2674 }
2639 2675 .changeset-status-container .changeset-status-lbl{
2640 2676 color: rgb(136, 136, 136);
2641 2677 float: left;
2642 2678 padding: 3px 4px 0px 0px
2643 2679 }
2644 2680 .code-header .changeset-status-container .changeset-status-lbl{
2645 2681 float: left;
2646 2682 padding: 0px 4px 0px 0px;
2647 2683 }
2648 2684 .changeset-status-container .changeset-status-ico{
2649 2685 float: left;
2650 2686 }
2651 2687 .code-header .changeset-status-container .changeset-status-ico, .container .changeset-status-ico{
2652 2688 float: left;
2653 2689 }
2654 2690 .right .comments-container{
2655 2691 padding-right: 5px;
2656 2692 margin-top:1px;
2657 2693 float:right;
2658 2694 height:14px;
2659 2695 }
2660 2696
2661 2697 .right .comments-cnt{
2662 2698 float: left;
2663 2699 color: rgb(136, 136, 136);
2664 2700 padding-right: 2px;
2665 2701 }
2666 2702
2667 2703 .right .changes{
2668 2704 clear: both;
2669 2705 }
2670 2706
2671 2707 .right .changes .changed_total {
2672 2708 display: block;
2673 2709 float: right;
2674 2710 text-align: center;
2675 2711 min-width: 45px;
2676 2712 cursor: pointer;
2677 2713 color: #444444;
2678 2714 background: #FEA;
2679 2715 -webkit-border-radius: 0px 0px 0px 6px;
2680 2716 -moz-border-radius: 0px 0px 0px 6px;
2681 2717 border-radius: 0px 0px 0px 6px;
2682 2718 padding: 1px;
2683 2719 }
2684 2720
2685 2721 .right .changes .added,.changed,.removed {
2686 2722 display: block;
2687 2723 padding: 1px;
2688 2724 color: #444444;
2689 2725 float: right;
2690 2726 text-align: center;
2691 2727 min-width: 15px;
2692 2728 }
2693 2729
2694 2730 .right .changes .added {
2695 2731 background: #CFC;
2696 2732 }
2697 2733
2698 2734 .right .changes .changed {
2699 2735 background: #FEA;
2700 2736 }
2701 2737
2702 2738 .right .changes .removed {
2703 2739 background: #FAA;
2704 2740 }
2705 2741
2706 2742 .right .merge {
2707 2743 padding: 1px 3px 1px 3px;
2708 2744 background-color: #fca062;
2709 2745 font-size: 10px;
2710 2746 font-weight: bold;
2711 2747 color: #ffffff;
2712 2748 text-transform: uppercase;
2713 2749 white-space: nowrap;
2714 2750 -webkit-border-radius: 3px;
2715 2751 -moz-border-radius: 3px;
2716 2752 border-radius: 3px;
2717 2753 margin-right: 2px;
2718 2754 }
2719 2755
2720 2756 .right .parent {
2721 2757 color: #666666;
2722 2758 clear:both;
2723 2759 }
2724 2760 .right .logtags{
2725 2761 padding: 2px 2px 2px 2px;
2726 2762 }
2727 2763 .right .logtags .branchtag,.right .logtags .tagtag,.right .logtags .booktag{
2728 2764 margin: 0px 2px;
2729 2765 }
2730 2766
2731 2767 .right .logtags .branchtag,.logtags .branchtag {
2732 2768 padding: 1px 3px 1px 3px;
2733 2769 background-color: #bfbfbf;
2734 2770 font-size: 10px;
2735 2771 font-weight: bold;
2736 2772 color: #ffffff;
2737 2773 text-transform: uppercase;
2738 2774 white-space: nowrap;
2739 2775 -webkit-border-radius: 3px;
2740 2776 -moz-border-radius: 3px;
2741 2777 border-radius: 3px;
2742 2778 }
2743 2779 .right .logtags .branchtag a:hover,.logtags .branchtag a{
2744 2780 color: #ffffff;
2745 2781 }
2746 2782 .right .logtags .branchtag a:hover,.logtags .branchtag a:hover{
2747 2783 text-decoration: none;
2748 2784 color: #ffffff;
2749 2785 }
2750 2786 .right .logtags .tagtag,.logtags .tagtag {
2751 2787 padding: 1px 3px 1px 3px;
2752 2788 background-color: #62cffc;
2753 2789 font-size: 10px;
2754 2790 font-weight: bold;
2755 2791 color: #ffffff;
2756 2792 text-transform: uppercase;
2757 2793 white-space: nowrap;
2758 2794 -webkit-border-radius: 3px;
2759 2795 -moz-border-radius: 3px;
2760 2796 border-radius: 3px;
2761 2797 }
2762 2798 .right .logtags .tagtag a:hover,.logtags .tagtag a{
2763 2799 color: #ffffff;
2764 2800 }
2765 2801 .right .logtags .tagtag a:hover,.logtags .tagtag a:hover{
2766 2802 text-decoration: none;
2767 2803 color: #ffffff;
2768 2804 }
2769 2805 .right .logbooks .bookbook,.logbooks .bookbook,.right .logtags .bookbook,.logtags .bookbook {
2770 2806 padding: 1px 3px 1px 3px;
2771 2807 background-color: #46A546;
2772 2808 font-size: 10px;
2773 2809 font-weight: bold;
2774 2810 color: #ffffff;
2775 2811 text-transform: uppercase;
2776 2812 white-space: nowrap;
2777 2813 -webkit-border-radius: 3px;
2778 2814 -moz-border-radius: 3px;
2779 2815 border-radius: 3px;
2780 2816 }
2781 2817 .right .logbooks .bookbook,.logbooks .bookbook a,.right .logtags .bookbook,.logtags .bookbook a{
2782 2818 color: #ffffff;
2783 2819 }
2784 2820 .right .logbooks .bookbook,.logbooks .bookbook a:hover,.right .logtags .bookbook,.logtags .bookbook a:hover{
2785 2821 text-decoration: none;
2786 2822 color: #ffffff;
2787 2823 }
2788 2824 div.browserblock {
2789 2825 overflow: hidden;
2790 2826 border: 1px solid #ccc;
2791 2827 background: #f8f8f8;
2792 2828 font-size: 100%;
2793 2829 line-height: 125%;
2794 2830 padding: 0;
2795 2831 -webkit-border-radius: 6px 6px 0px 0px;
2796 2832 -moz-border-radius: 6px 6px 0px 0px;
2797 2833 border-radius: 6px 6px 0px 0px;
2798 2834 }
2799 2835
2800 2836 div.browserblock .browser-header {
2801 2837 background: #FFF;
2802 2838 padding: 10px 0px 15px 0px;
2803 2839 width: 100%;
2804 2840 }
2805 2841
2806 2842 div.browserblock .browser-nav {
2807 2843 float: left
2808 2844 }
2809 2845
2810 2846 div.browserblock .browser-branch {
2811 2847 float: left;
2812 2848 }
2813 2849
2814 2850 div.browserblock .browser-branch label {
2815 2851 color: #4A4A4A;
2816 2852 vertical-align: text-top;
2817 2853 }
2818 2854
2819 2855 div.browserblock .browser-header span {
2820 2856 margin-left: 5px;
2821 2857 font-weight: 700;
2822 2858 }
2823 2859
2824 2860 div.browserblock .browser-search {
2825 2861 clear: both;
2826 2862 padding: 8px 8px 0px 5px;
2827 2863 height: 20px;
2828 2864 }
2829 2865
2830 2866 div.browserblock #node_filter_box {
2831 2867
2832 2868 }
2833 2869
2834 2870 div.browserblock .search_activate {
2835 2871 float: left
2836 2872 }
2837 2873
2838 2874 div.browserblock .add_node {
2839 2875 float: left;
2840 2876 padding-left: 5px;
2841 2877 }
2842 2878
2843 2879 div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover
2844 2880 {
2845 2881 text-decoration: none !important;
2846 2882 }
2847 2883
2848 2884 div.browserblock .browser-body {
2849 2885 background: #EEE;
2850 2886 border-top: 1px solid #CCC;
2851 2887 }
2852 2888
2853 2889 table.code-browser {
2854 2890 border-collapse: collapse;
2855 2891 width: 100%;
2856 2892 }
2857 2893
2858 2894 table.code-browser tr {
2859 2895 margin: 3px;
2860 2896 }
2861 2897
2862 2898 table.code-browser thead th {
2863 2899 background-color: #EEE;
2864 2900 height: 20px;
2865 2901 font-size: 1.1em;
2866 2902 font-weight: 700;
2867 2903 text-align: left;
2868 2904 padding-left: 10px;
2869 2905 }
2870 2906
2871 2907 table.code-browser tbody td {
2872 2908 padding-left: 10px;
2873 2909 height: 20px;
2874 2910 }
2875 2911
2876 2912 table.code-browser .browser-file {
2877 2913 background: url("../images/icons/document_16.png") no-repeat scroll 3px;
2878 2914 height: 16px;
2879 2915 padding-left: 20px;
2880 2916 text-align: left;
2881 2917 }
2882 2918 .diffblock .changeset_header {
2883 2919 height: 16px;
2884 2920 }
2885 2921 .diffblock .changeset_file {
2886 2922 background: url("../images/icons/file.png") no-repeat scroll 3px;
2887 2923 text-align: left;
2888 2924 float: left;
2889 2925 padding: 2px 0px 2px 22px;
2890 2926 }
2891 2927 .diffblock .diff-menu-wrapper{
2892 2928 float: left;
2893 2929 }
2894 2930
2895 2931 .diffblock .diff-menu{
2896 2932 position: absolute;
2897 2933 background: none repeat scroll 0 0 #FFFFFF;
2898 2934 border-color: #003367 #666666 #666666;
2899 2935 border-right: 1px solid #666666;
2900 2936 border-style: solid solid solid;
2901 2937 border-width: 1px;
2902 2938 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
2903 2939 margin-top:5px;
2904 2940 margin-left:1px;
2905 2941
2906 2942 }
2907 2943 .diffblock .diff-actions {
2908 2944 padding: 2px 0px 0px 2px;
2909 2945 float: left;
2910 2946 }
2911 2947 .diffblock .diff-menu ul li {
2912 2948 padding: 0px 0px 0px 0px !important;
2913 2949 }
2914 2950 .diffblock .diff-menu ul li a{
2915 2951 display: block;
2916 2952 padding: 3px 8px 3px 8px !important;
2917 2953 }
2918 2954 .diffblock .diff-menu ul li a:hover{
2919 2955 text-decoration: none;
2920 2956 background-color: #EEEEEE;
2921 2957 }
2922 2958 table.code-browser .browser-dir {
2923 2959 background: url("../images/icons/folder_16.png") no-repeat scroll 3px;
2924 2960 height: 16px;
2925 2961 padding-left: 20px;
2926 2962 text-align: left;
2927 2963 }
2928 2964
2929 2965 table.code-browser .submodule-dir {
2930 2966 background: url("../images/icons/disconnect.png") no-repeat scroll 3px;
2931 2967 height: 16px;
2932 2968 padding-left: 20px;
2933 2969 text-align: left;
2934 2970 }
2935 2971
2936 2972
2937 2973 .box .search {
2938 2974 clear: both;
2939 2975 overflow: hidden;
2940 2976 margin: 0;
2941 2977 padding: 0 20px 10px;
2942 2978 }
2943 2979
2944 2980 .box .search div.search_path {
2945 2981 background: none repeat scroll 0 0 #EEE;
2946 2982 border: 1px solid #CCC;
2947 2983 color: blue;
2948 2984 margin-bottom: 10px;
2949 2985 padding: 10px 0;
2950 2986 }
2951 2987
2952 2988 .box .search div.search_path div.link {
2953 2989 font-weight: 700;
2954 2990 margin-left: 25px;
2955 2991 }
2956 2992
2957 2993 .box .search div.search_path div.link a {
2958 2994 color: #003367;
2959 2995 cursor: pointer;
2960 2996 text-decoration: none;
2961 2997 }
2962 2998
2963 2999 #path_unlock {
2964 3000 color: red;
2965 3001 font-size: 1.2em;
2966 3002 padding-left: 4px;
2967 3003 }
2968 3004
2969 3005 .info_box span {
2970 3006 margin-left: 3px;
2971 3007 margin-right: 3px;
2972 3008 }
2973 3009
2974 3010 .info_box .rev {
2975 3011 color: #003367;
2976 3012 font-size: 1.6em;
2977 3013 font-weight: bold;
2978 3014 vertical-align: sub;
2979 3015 }
2980 3016
2981 3017 .info_box input#at_rev,.info_box input#size {
2982 3018 background: #FFF;
2983 3019 border-top: 1px solid #b3b3b3;
2984 3020 border-left: 1px solid #b3b3b3;
2985 3021 border-right: 1px solid #eaeaea;
2986 3022 border-bottom: 1px solid #eaeaea;
2987 3023 color: #000;
2988 3024 font-size: 12px;
2989 3025 margin: 0;
2990 3026 padding: 1px 5px 1px;
2991 3027 }
2992 3028
2993 3029 .info_box input#view {
2994 3030 text-align: center;
2995 3031 padding: 4px 3px 2px 2px;
2996 3032 }
2997 3033
2998 3034 .yui-overlay,.yui-panel-container {
2999 3035 visibility: hidden;
3000 3036 position: absolute;
3001 3037 z-index: 2;
3002 3038 }
3003 3039
3004 3040 #tip-box {
3005 3041 position: absolute;
3006 3042
3007 3043 background-color: #FFF;
3008 3044 border: 2px solid #003367;
3009 3045 font: 100% sans-serif;
3010 3046 width: auto;
3011 3047 opacity: 1px;
3012 3048 padding: 8px;
3013 3049
3014 3050 white-space: pre-wrap;
3015 3051 -webkit-border-radius: 8px 8px 8px 8px;
3016 3052 -khtml-border-radius: 8px 8px 8px 8px;
3017 3053 -moz-border-radius: 8px 8px 8px 8px;
3018 3054 border-radius: 8px 8px 8px 8px;
3019 3055 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3020 3056 -moz-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3021 3057 -webkit-box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
3022 3058 }
3023 3059
3024 3060 .mentions-container{
3025 3061 width: 90% !important;
3026 3062 }
3027 3063 .mentions-container .yui-ac-content{
3028 3064 width: 100% !important;
3029 3065 }
3030 3066
3031 3067 .ac {
3032 3068 vertical-align: top;
3033 3069 }
3034 3070
3035 3071 .ac .yui-ac {
3036 3072 position: inherit;
3037 3073 font-size: 100%;
3038 3074 }
3039 3075
3040 3076 .ac .perm_ac {
3041 3077 width: 20em;
3042 3078 }
3043 3079
3044 3080 .ac .yui-ac-input {
3045 3081 width: 100%;
3046 3082 }
3047 3083
3048 3084 .ac .yui-ac-container {
3049 3085 position: absolute;
3050 3086 top: 1.6em;
3051 3087 width: auto;
3052 3088 }
3053 3089
3054 3090 .ac .yui-ac-content {
3055 3091 position: absolute;
3056 3092 border: 1px solid gray;
3057 3093 background: #fff;
3058 3094 z-index: 9050;
3059 3095
3060 3096 }
3061 3097
3062 3098 .ac .yui-ac-shadow {
3063 3099 position: absolute;
3064 3100 width: 100%;
3065 3101 background: #000;
3066 3102 -moz-opacity: 0.1px;
3067 3103 opacity: .10;
3068 3104 filter: alpha(opacity = 10);
3069 3105 z-index: 9049;
3070 3106 margin: .3em;
3071 3107 }
3072 3108
3073 3109 .ac .yui-ac-content ul {
3074 3110 width: 100%;
3075 3111 margin: 0;
3076 3112 padding: 0;
3077 3113 z-index: 9050;
3078 3114 }
3079 3115
3080 3116 .ac .yui-ac-content li {
3081 3117 cursor: default;
3082 3118 white-space: nowrap;
3083 3119 margin: 0;
3084 3120 padding: 2px 5px;
3085 3121 height: 18px;
3086 3122 z-index: 9050;
3087 3123 display: block;
3088 3124 width: auto !important;
3089 3125 }
3090 3126
3091 3127 .ac .yui-ac-content li .ac-container-wrap{
3092 3128 width: auto;
3093 3129 }
3094 3130
3095 3131 .ac .yui-ac-content li.yui-ac-prehighlight {
3096 3132 background: #B3D4FF;
3097 3133 z-index: 9050;
3098 3134 }
3099 3135
3100 3136 .ac .yui-ac-content li.yui-ac-highlight {
3101 3137 background: #556CB5;
3102 3138 color: #FFF;
3103 3139 z-index: 9050;
3104 3140 }
3105 3141 .ac .yui-ac-bd{
3106 3142 z-index: 9050;
3107 3143 }
3108 3144
3109 3145 .follow {
3110 3146 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
3111 3147 height: 16px;
3112 3148 width: 20px;
3113 3149 cursor: pointer;
3114 3150 display: block;
3115 3151 float: right;
3116 3152 margin-top: 2px;
3117 3153 }
3118 3154
3119 3155 .following {
3120 3156 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
3121 3157 height: 16px;
3122 3158 width: 20px;
3123 3159 cursor: pointer;
3124 3160 display: block;
3125 3161 float: right;
3126 3162 margin-top: 2px;
3127 3163 }
3128 3164
3129 3165 .locking_locked{
3130 3166 background: #FFF url("../images/icons/block_16.png") no-repeat scroll 3px;
3131 3167 height: 16px;
3132 3168 width: 20px;
3133 3169 cursor: pointer;
3134 3170 display: block;
3135 3171 float: right;
3136 3172 margin-top: 2px;
3137 3173 }
3138 3174
3139 3175 .locking_unlocked{
3140 3176 background: #FFF url("../images/icons/accept.png") no-repeat scroll 3px;
3141 3177 height: 16px;
3142 3178 width: 20px;
3143 3179 cursor: pointer;
3144 3180 display: block;
3145 3181 float: right;
3146 3182 margin-top: 2px;
3147 3183 }
3148 3184
3149 3185 .currently_following {
3150 3186 padding-left: 10px;
3151 3187 padding-bottom: 5px;
3152 3188 }
3153 3189
3154 3190 .add_icon {
3155 3191 background: url("../images/icons/add.png") no-repeat scroll 3px;
3156 3192 padding-left: 20px;
3157 3193 padding-top: 0px;
3158 3194 text-align: left;
3159 3195 }
3160 3196
3161 3197 .accept_icon {
3162 3198 background: url("../images/icons/accept.png") no-repeat scroll 3px;
3163 3199 padding-left: 20px;
3164 3200 padding-top: 0px;
3165 3201 text-align: left;
3166 3202 }
3167 3203
3168 3204 .edit_icon {
3169 3205 background: url("../images/icons/folder_edit.png") no-repeat scroll 3px;
3170 3206 padding-left: 20px;
3171 3207 padding-top: 0px;
3172 3208 text-align: left;
3173 3209 }
3174 3210
3175 3211 .delete_icon {
3176 3212 background: url("../images/icons/delete.png") no-repeat scroll 3px;
3177 3213 padding-left: 20px;
3178 3214 padding-top: 0px;
3179 3215 text-align: left;
3180 3216 }
3181 3217
3182 3218 .refresh_icon {
3183 3219 background: url("../images/icons/arrow_refresh.png") no-repeat scroll
3184 3220 3px;
3185 3221 padding-left: 20px;
3186 3222 padding-top: 0px;
3187 3223 text-align: left;
3188 3224 }
3189 3225
3190 3226 .pull_icon {
3191 3227 background: url("../images/icons/connect.png") no-repeat scroll 3px;
3192 3228 padding-left: 20px;
3193 3229 padding-top: 0px;
3194 3230 text-align: left;
3195 3231 }
3196 3232
3197 3233 .rss_icon {
3198 3234 background: url("../images/icons/rss_16.png") no-repeat scroll 3px;
3199 3235 padding-left: 20px;
3200 3236 padding-top: 4px;
3201 3237 text-align: left;
3202 3238 font-size: 8px
3203 3239 }
3204 3240
3205 3241 .atom_icon {
3206 3242 background: url("../images/icons/atom.png") no-repeat scroll 3px;
3207 3243 padding-left: 20px;
3208 3244 padding-top: 4px;
3209 3245 text-align: left;
3210 3246 font-size: 8px
3211 3247 }
3212 3248
3213 3249 .archive_icon {
3214 3250 background: url("../images/icons/compress.png") no-repeat scroll 3px;
3215 3251 padding-left: 20px;
3216 3252 text-align: left;
3217 3253 padding-top: 1px;
3218 3254 }
3219 3255
3220 3256 .start_following_icon {
3221 3257 background: url("../images/icons/heart_add.png") no-repeat scroll 3px;
3222 3258 padding-left: 20px;
3223 3259 text-align: left;
3224 3260 padding-top: 0px;
3225 3261 }
3226 3262
3227 3263 .stop_following_icon {
3228 3264 background: url("../images/icons/heart_delete.png") no-repeat scroll 3px;
3229 3265 padding-left: 20px;
3230 3266 text-align: left;
3231 3267 padding-top: 0px;
3232 3268 }
3233 3269
3234 3270 .action_button {
3235 3271 border: 0;
3236 3272 display: inline;
3237 3273 }
3238 3274
3239 3275 .action_button:hover {
3240 3276 border: 0;
3241 3277 text-decoration: underline;
3242 3278 cursor: pointer;
3243 3279 }
3244 3280
3245 3281 #switch_repos {
3246 3282 position: absolute;
3247 3283 height: 25px;
3248 3284 z-index: 1;
3249 3285 }
3250 3286
3251 3287 #switch_repos select {
3252 3288 min-width: 150px;
3253 3289 max-height: 250px;
3254 3290 z-index: 1;
3255 3291 }
3256 3292
3257 3293 .breadcrumbs {
3258 3294 border: medium none;
3259 3295 color: #FFF;
3260 3296 float: left;
3261 3297 text-transform: uppercase;
3262 3298 font-weight: 700;
3263 3299 font-size: 14px;
3264 3300 margin: 0;
3265 3301 padding: 11px 0 11px 10px;
3266 3302 }
3267 3303
3268 3304 .breadcrumbs .hash {
3269 3305 text-transform: none;
3270 3306 color: #fff;
3271 3307 }
3272 3308
3273 3309 .breadcrumbs a {
3274 3310 color: #FFF;
3275 3311 }
3276 3312
3277 3313 .flash_msg {
3278 3314
3279 3315 }
3280 3316
3281 3317 .flash_msg ul {
3282 3318
3283 3319 }
3284 3320
3285 3321 .error_red {
3286 3322 color:red;
3287 3323 }
3288 3324
3289 3325 .error_msg {
3290 3326 background-color: #c43c35;
3291 3327 background-repeat: repeat-x;
3292 3328 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35) );
3293 3329 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3294 3330 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3295 3331 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35) );
3296 3332 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3297 3333 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3298 3334 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3299 3335 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b',endColorstr='#c43c35', GradientType=0 );
3300 3336 border-color: #c43c35 #c43c35 #882a25;
3301 3337 }
3302 3338
3303 3339 .warning_msg {
3304 3340 color: #404040 !important;
3305 3341 background-color: #eedc94;
3306 3342 background-repeat: repeat-x;
3307 3343 background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94) );
3308 3344 background-image: -moz-linear-gradient(top, #fceec1, #eedc94);
3309 3345 background-image: -ms-linear-gradient(top, #fceec1, #eedc94);
3310 3346 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94) );
3311 3347 background-image: -webkit-linear-gradient(top, #fceec1, #eedc94);
3312 3348 background-image: -o-linear-gradient(top, #fceec1, #eedc94);
3313 3349 background-image: linear-gradient(top, #fceec1, #eedc94);
3314 3350 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0 );
3315 3351 border-color: #eedc94 #eedc94 #e4c652;
3316 3352 }
3317 3353
3318 3354 .success_msg {
3319 3355 background-color: #57a957;
3320 3356 background-repeat: repeat-x !important;
3321 3357 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957) );
3322 3358 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3323 3359 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3324 3360 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957) );
3325 3361 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3326 3362 background-image: -o-linear-gradient(top, #62c462, #57a957);
3327 3363 background-image: linear-gradient(top, #62c462, #57a957);
3328 3364 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0 );
3329 3365 border-color: #57a957 #57a957 #3d773d;
3330 3366 }
3331 3367
3332 3368 .notice_msg {
3333 3369 background-color: #339bb9;
3334 3370 background-repeat: repeat-x;
3335 3371 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9) );
3336 3372 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3337 3373 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3338 3374 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9) );
3339 3375 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3340 3376 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3341 3377 background-image: linear-gradient(top, #5bc0de, #339bb9);
3342 3378 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0 );
3343 3379 border-color: #339bb9 #339bb9 #22697d;
3344 3380 }
3345 3381
3346 3382 .success_msg,.error_msg,.notice_msg,.warning_msg {
3347 3383 font-size: 12px;
3348 3384 font-weight: 700;
3349 3385 min-height: 14px;
3350 3386 line-height: 14px;
3351 3387 margin-bottom: 10px;
3352 3388 margin-top: 0;
3353 3389 display: block;
3354 3390 overflow: auto;
3355 3391 padding: 6px 10px 6px 10px;
3356 3392 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3357 3393 position: relative;
3358 3394 color: #FFF;
3359 3395 border-width: 1px;
3360 3396 border-style: solid;
3361 3397 -webkit-border-radius: 4px;
3362 3398 -moz-border-radius: 4px;
3363 3399 border-radius: 4px;
3364 3400 -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3365 3401 -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3366 3402 box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25);
3367 3403 }
3368 3404
3369 3405 #msg_close {
3370 3406 background: transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
3371 3407 cursor: pointer;
3372 3408 height: 16px;
3373 3409 position: absolute;
3374 3410 right: 5px;
3375 3411 top: 5px;
3376 3412 width: 16px;
3377 3413 }
3378 3414 div#legend_data{
3379 3415 padding-left:10px;
3380 3416 }
3381 3417 div#legend_container table{
3382 3418 border: none !important;
3383 3419 }
3384 3420 div#legend_container table,div#legend_choices table {
3385 3421 width: auto !important;
3386 3422 }
3387 3423
3388 3424 table#permissions_manage {
3389 3425 width: 0 !important;
3390 3426 }
3391 3427
3392 3428 table#permissions_manage span.private_repo_msg {
3393 3429 font-size: 0.8em;
3394 3430 opacity: 0.6px;
3395 3431 }
3396 3432
3397 3433 table#permissions_manage td.private_repo_msg {
3398 3434 font-size: 0.8em;
3399 3435 }
3400 3436
3401 3437 table#permissions_manage tr#add_perm_input td {
3402 3438 vertical-align: middle;
3403 3439 }
3404 3440
3405 3441 div.gravatar {
3406 3442 background-color: #FFF;
3407 3443 float: left;
3408 3444 margin-right: 0.7em;
3409 3445 padding: 1px 1px 1px 1px;
3410 3446 line-height:0;
3411 3447 -webkit-border-radius: 3px;
3412 3448 -khtml-border-radius: 3px;
3413 3449 -moz-border-radius: 3px;
3414 3450 border-radius: 3px;
3415 3451 }
3416 3452
3417 3453 div.gravatar img {
3418 3454 -webkit-border-radius: 2px;
3419 3455 -khtml-border-radius: 2px;
3420 3456 -moz-border-radius: 2px;
3421 3457 border-radius: 2px;
3422 3458 }
3423 3459
3424 3460 #header,#content,#footer {
3425 3461 min-width: 978px;
3426 3462 }
3427 3463
3428 3464 #content {
3429 3465 clear: both;
3430 3466 overflow: hidden;
3431 3467 padding: 54px 10px 14px 10px;
3432 3468 }
3433 3469
3434 3470 #content div.box div.title div.search {
3435 3471
3436 3472 border-left: 1px solid #316293;
3437 3473 }
3438 3474
3439 3475 #content div.box div.title div.search div.input input {
3440 3476 border: 1px solid #316293;
3441 3477 }
3442 3478
3443 3479 .ui-btn{
3444 3480 color: #515151;
3445 3481 background-color: #DADADA;
3446 3482 background-repeat: repeat-x;
3447 3483 background-image: -khtml-gradient(linear, left top, left bottom, from(#F4F4F4),to(#DADADA) );
3448 3484 background-image: -moz-linear-gradient(top, #F4F4F4, #DADADA);
3449 3485 background-image: -ms-linear-gradient(top, #F4F4F4, #DADADA);
3450 3486 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #F4F4F4),color-stop(100%, #DADADA) );
3451 3487 background-image: -webkit-linear-gradient(top, #F4F4F4, #DADADA) );
3452 3488 background-image: -o-linear-gradient(top, #F4F4F4, #DADADA) );
3453 3489 background-image: linear-gradient(top, #F4F4F4, #DADADA);
3454 3490 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#F4F4F4', endColorstr='#DADADA', GradientType=0);
3455 3491
3456 3492 border-top: 1px solid #DDD;
3457 3493 border-left: 1px solid #c6c6c6;
3458 3494 border-right: 1px solid #DDD;
3459 3495 border-bottom: 1px solid #c6c6c6;
3460 3496 color: #515151;
3461 3497 outline: none;
3462 3498 margin: 0px 3px 3px 0px;
3463 3499 -webkit-border-radius: 4px 4px 4px 4px !important;
3464 3500 -khtml-border-radius: 4px 4px 4px 4px !important;
3465 3501 -moz-border-radius: 4px 4px 4px 4px !important;
3466 3502 border-radius: 4px 4px 4px 4px !important;
3467 3503 cursor: pointer !important;
3468 3504 padding: 3px 3px 3px 3px;
3469 3505 background-position: 0 -15px;
3470 3506
3471 3507 }
3472 3508 .ui-btn.xsmall{
3473 3509 padding: 1px 2px 1px 1px;
3474 3510 }
3475 3511
3476 3512 .ui-btn.large{
3477 3513 padding: 6px 12px;
3478 3514 }
3479 3515
3480 3516 .ui-btn.clone{
3481 3517 padding: 5px 2px 6px 1px;
3482 3518 margin: 0px -4px 3px 0px;
3483 3519 -webkit-border-radius: 4px 0px 0px 4px !important;
3484 3520 -khtml-border-radius: 4px 0px 0px 4px !important;
3485 3521 -moz-border-radius: 4px 0px 0px 4px !important;
3486 3522 border-radius: 4px 0px 0px 4px !important;
3487 3523 width: 100px;
3488 3524 text-align: center;
3489 3525 float: left;
3490 3526 position: absolute;
3491 3527 }
3492 3528 .ui-btn:focus {
3493 3529 outline: none;
3494 3530 }
3495 3531 .ui-btn:hover{
3496 3532 background-position: 0 0px;
3497 3533 text-decoration: none;
3498 3534 color: #515151;
3499 3535 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 0 3px #FFFFFF !important;
3500 3536 }
3501 3537
3502 3538 .ui-btn.red{
3503 3539 color:#fff;
3504 3540 background-color: #c43c35;
3505 3541 background-repeat: repeat-x;
3506 3542 background-image: -khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));
3507 3543 background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
3508 3544 background-image: -ms-linear-gradient(top, #ee5f5b, #c43c35);
3509 3545 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));
3510 3546 background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
3511 3547 background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
3512 3548 background-image: linear-gradient(top, #ee5f5b, #c43c35);
3513 3549 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);
3514 3550 border-color: #c43c35 #c43c35 #882a25;
3515 3551 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3516 3552 }
3517 3553
3518 3554
3519 3555 .ui-btn.blue{
3520 3556 color:#fff;
3521 3557 background-color: #339bb9;
3522 3558 background-repeat: repeat-x;
3523 3559 background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));
3524 3560 background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
3525 3561 background-image: -ms-linear-gradient(top, #5bc0de, #339bb9);
3526 3562 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));
3527 3563 background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
3528 3564 background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
3529 3565 background-image: linear-gradient(top, #5bc0de, #339bb9);
3530 3566 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);
3531 3567 border-color: #339bb9 #339bb9 #22697d;
3532 3568 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3533 3569 }
3534 3570
3535 3571 .ui-btn.green{
3536 3572 background-color: #57a957;
3537 3573 background-repeat: repeat-x;
3538 3574 background-image: -khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));
3539 3575 background-image: -moz-linear-gradient(top, #62c462, #57a957);
3540 3576 background-image: -ms-linear-gradient(top, #62c462, #57a957);
3541 3577 background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));
3542 3578 background-image: -webkit-linear-gradient(top, #62c462, #57a957);
3543 3579 background-image: -o-linear-gradient(top, #62c462, #57a957);
3544 3580 background-image: linear-gradient(top, #62c462, #57a957);
3545 3581 filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);
3546 3582 border-color: #57a957 #57a957 #3d773d;
3547 3583 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
3548 3584 }
3549 3585
3550 3586 .ui-btn.active{
3551 3587 font-weight: bold;
3552 3588 }
3553 3589
3554 3590 ins,div.options a:hover {
3555 3591 text-decoration: none;
3556 3592 }
3557 3593
3558 3594 img,
3559 3595 #header #header-inner #quick li a:hover span.normal,
3560 3596 #header #header-inner #quick li ul li.last,
3561 3597 #content div.box div.form div.fields div.field div.textarea table td table td a,
3562 3598 #clone_url,
3563 3599 #clone_url_id
3564 3600 {
3565 3601 border: none;
3566 3602 }
3567 3603
3568 3604 img.icon,.right .merge img {
3569 3605 vertical-align: bottom;
3570 3606 }
3571 3607
3572 3608 #header ul#logged-user,#content div.box div.title ul.links,
3573 3609 #content div.box div.message div.dismiss,
3574 3610 #content div.box div.traffic div.legend ul
3575 3611 {
3576 3612 float: right;
3577 3613 margin: 0;
3578 3614 padding: 0;
3579 3615 }
3580 3616
3581 3617 #header #header-inner #home,#header #header-inner #logo,
3582 3618 #content div.box ul.left,#content div.box ol.left,
3583 3619 #content div.box div.pagination-left,div#commit_history,
3584 3620 div#legend_data,div#legend_container,div#legend_choices
3585 3621 {
3586 3622 float: left;
3587 3623 }
3588 3624
3589 3625 #header #header-inner #quick li:hover ul ul,
3590 3626 #header #header-inner #quick li:hover ul ul ul,
3591 3627 #header #header-inner #quick li:hover ul ul ul ul,
3592 3628 #content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow
3593 3629 {
3594 3630 display: none;
3595 3631 }
3596 3632
3597 3633 #header #header-inner #quick li:hover ul,#header #header-inner #quick li li:hover ul,#header #header-inner #quick li li li:hover ul,#header #header-inner #quick li li li li:hover ul,#content #left #menu ul.opened,#content #left #menu li ul.expanded
3598 3634 {
3599 3635 display: block;
3600 3636 }
3601 3637
3602 3638 #content div.graph {
3603 3639 padding: 0 10px 10px;
3604 3640 }
3605 3641
3606 3642 #content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a
3607 3643 {
3608 3644 color: #bfe3ff;
3609 3645 }
3610 3646
3611 3647 #content div.box ol.lower-roman,#content div.box ol.upper-roman,#content div.box ol.lower-alpha,#content div.box ol.upper-alpha,#content div.box ol.decimal
3612 3648 {
3613 3649 margin: 10px 24px 10px 44px;
3614 3650 }
3615 3651
3616 3652 #content div.box div.form,#content div.box div.table,#content div.box div.traffic
3617 3653 {
3618 3654 clear: both;
3619 3655 overflow: hidden;
3620 3656 margin: 0;
3621 3657 padding: 0 20px 10px;
3622 3658 }
3623 3659
3624 3660 #content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields
3625 3661 {
3626 3662 clear: both;
3627 3663 overflow: hidden;
3628 3664 margin: 0;
3629 3665 padding: 0;
3630 3666 }
3631 3667
3632 3668 #content div.box div.form div.fields div.field div.label span,#login div.form div.fields div.field div.label span,#register div.form div.fields div.field div.label span
3633 3669 {
3634 3670 height: 1%;
3635 3671 display: block;
3636 3672 color: #363636;
3637 3673 margin: 0;
3638 3674 padding: 2px 0 0;
3639 3675 }
3640 3676
3641 3677 #content div.box div.form div.fields div.field div.input input.error,#login div.form div.fields div.field div.input input.error,#register div.form div.fields div.field div.input input.error
3642 3678 {
3643 3679 background: #FBE3E4;
3644 3680 border-top: 1px solid #e1b2b3;
3645 3681 border-left: 1px solid #e1b2b3;
3646 3682 border-right: 1px solid #FBC2C4;
3647 3683 border-bottom: 1px solid #FBC2C4;
3648 3684 }
3649 3685
3650 3686 #content div.box div.form div.fields div.field div.input input.success,#login div.form div.fields div.field div.input input.success,#register div.form div.fields div.field div.input input.success
3651 3687 {
3652 3688 background: #E6EFC2;
3653 3689 border-top: 1px solid #cebb98;
3654 3690 border-left: 1px solid #cebb98;
3655 3691 border-right: 1px solid #c6d880;
3656 3692 border-bottom: 1px solid #c6d880;
3657 3693 }
3658 3694
3659 3695 #content div.box-left div.form div.fields div.field div.textarea,#content div.box-right div.form div.fields div.field div.textarea,#content div.box div.form div.fields div.field div.select select,#content div.box table th.selected input,#content div.box table td.selected input
3660 3696 {
3661 3697 margin: 0;
3662 3698 }
3663 3699
3664 3700 #content div.box-left div.form div.fields div.field div.select,#content div.box-left div.form div.fields div.field div.checkboxes,#content div.box-left div.form div.fields div.field div.radios,#content div.box-right div.form div.fields div.field div.select,#content div.box-right div.form div.fields div.field div.checkboxes,#content div.box-right div.form div.fields div.field div.radios
3665 3701 {
3666 3702 margin: 0 0 0 0px !important;
3667 3703 padding: 0;
3668 3704 }
3669 3705
3670 3706 #content div.box div.form div.fields div.field div.select,#content div.box div.form div.fields div.field div.checkboxes,#content div.box div.form div.fields div.field div.radios
3671 3707 {
3672 3708 margin: 0 0 0 200px;
3673 3709 padding: 0;
3674 3710 }
3675 3711
3676 3712 #content div.box div.form div.fields div.field div.select a:hover,#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,#content div.box div.action a:hover
3677 3713 {
3678 3714 color: #000;
3679 3715 text-decoration: none;
3680 3716 }
3681 3717
3682 3718 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus
3683 3719 {
3684 3720 border: 1px solid #666;
3685 3721 }
3686 3722
3687 3723 #content div.box div.form div.fields div.field div.checkboxes div.checkbox,#content div.box div.form div.fields div.field div.radios div.radio
3688 3724 {
3689 3725 clear: both;
3690 3726 overflow: hidden;
3691 3727 margin: 0;
3692 3728 padding: 8px 0 2px;
3693 3729 }
3694 3730
3695 3731 #content div.box div.form div.fields div.field div.checkboxes div.checkbox input,#content div.box div.form div.fields div.field div.radios div.radio input
3696 3732 {
3697 3733 float: left;
3698 3734 margin: 0;
3699 3735 }
3700 3736
3701 3737 #content div.box div.form div.fields div.field div.checkboxes div.checkbox label,#content div.box div.form div.fields div.field div.radios div.radio label
3702 3738 {
3703 3739 height: 1%;
3704 3740 display: block;
3705 3741 float: left;
3706 3742 margin: 2px 0 0 4px;
3707 3743 }
3708 3744
3709 3745 div.form div.fields div.field div.button input,
3710 3746 #content div.box div.form div.fields div.buttons input
3711 3747 div.form div.fields div.buttons input,
3712 3748 #content div.box div.action div.button input {
3713 3749 /*color: #000;*/
3714 3750 font-size: 11px;
3715 3751 font-weight: 700;
3716 3752 margin: 0;
3717 3753 }
3718 3754
3719 3755 input.ui-button {
3720 3756 background: #e5e3e3 url("../images/button.png") repeat-x;
3721 3757 border-top: 1px solid #DDD;
3722 3758 border-left: 1px solid #c6c6c6;
3723 3759 border-right: 1px solid #DDD;
3724 3760 border-bottom: 1px solid #c6c6c6;
3725 3761 color: #515151 !important;
3726 3762 outline: none;
3727 3763 margin: 0;
3728 3764 padding: 6px 12px;
3729 3765 -webkit-border-radius: 4px 4px 4px 4px;
3730 3766 -khtml-border-radius: 4px 4px 4px 4px;
3731 3767 -moz-border-radius: 4px 4px 4px 4px;
3732 3768 border-radius: 4px 4px 4px 4px;
3733 3769 box-shadow: 0 1px 0 #ececec;
3734 3770 cursor: pointer;
3735 3771 }
3736 3772
3737 3773 input.ui-button:hover {
3738 3774 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
3739 3775 border-top: 1px solid #ccc;
3740 3776 border-left: 1px solid #bebebe;
3741 3777 border-right: 1px solid #b1b1b1;
3742 3778 border-bottom: 1px solid #afafaf;
3743 3779 }
3744 3780
3745 3781 div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight
3746 3782 {
3747 3783 display: inline;
3748 3784 }
3749 3785
3750 3786 #content div.box div.form div.fields div.buttons,div.form div.fields div.buttons
3751 3787 {
3752 3788 margin: 10px 0 0 200px;
3753 3789 padding: 0;
3754 3790 }
3755 3791
3756 3792 #content div.box-left div.form div.fields div.buttons,#content div.box-right div.form div.fields div.buttons,div.box-left div.form div.fields div.buttons,div.box-right div.form div.fields div.buttons
3757 3793 {
3758 3794 margin: 10px 0 0;
3759 3795 }
3760 3796
3761 3797 #content div.box table td.user,#content div.box table td.address {
3762 3798 width: 10%;
3763 3799 text-align: center;
3764 3800 }
3765 3801
3766 3802 #content div.box div.action div.button,#login div.form div.fields div.field div.input div.link,#register div.form div.fields div.field div.input div.link
3767 3803 {
3768 3804 text-align: right;
3769 3805 margin: 6px 0 0;
3770 3806 padding: 0;
3771 3807 }
3772 3808
3773 3809 #content div.box div.action div.button input.ui-state-hover,#login div.form div.fields div.buttons input.ui-state-hover,#register div.form div.fields div.buttons input.ui-state-hover
3774 3810 {
3775 3811 background: #b4b4b4 url("../images/button_selected.png") repeat-x;
3776 3812 border-top: 1px solid #ccc;
3777 3813 border-left: 1px solid #bebebe;
3778 3814 border-right: 1px solid #b1b1b1;
3779 3815 border-bottom: 1px solid #afafaf;
3780 3816 color: #515151;
3781 3817 margin: 0;
3782 3818 padding: 6px 12px;
3783 3819 }
3784 3820
3785 3821 #content div.box div.pagination div.results,#content div.box div.pagination-wh div.results
3786 3822 {
3787 3823 text-align: left;
3788 3824 float: left;
3789 3825 margin: 0;
3790 3826 padding: 0;
3791 3827 }
3792 3828
3793 3829 #content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span
3794 3830 {
3795 3831 height: 1%;
3796 3832 display: block;
3797 3833 float: left;
3798 3834 background: #ebebeb url("../images/pager.png") repeat-x;
3799 3835 border-top: 1px solid #dedede;
3800 3836 border-left: 1px solid #cfcfcf;
3801 3837 border-right: 1px solid #c4c4c4;
3802 3838 border-bottom: 1px solid #c4c4c4;
3803 3839 color: #4A4A4A;
3804 3840 font-weight: 700;
3805 3841 margin: 0;
3806 3842 padding: 6px 8px;
3807 3843 }
3808 3844
3809 3845 #content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled
3810 3846 {
3811 3847 color: #B4B4B4;
3812 3848 padding: 6px;
3813 3849 }
3814 3850
3815 3851 #login,#register {
3816 3852 width: 520px;
3817 3853 margin: 10% auto 0;
3818 3854 padding: 0;
3819 3855 }
3820 3856
3821 3857 #login div.color,#register div.color {
3822 3858 clear: both;
3823 3859 overflow: hidden;
3824 3860 background: #FFF;
3825 3861 margin: 10px auto 0;
3826 3862 padding: 3px 3px 3px 0;
3827 3863 }
3828 3864
3829 3865 #login div.color a,#register div.color a {
3830 3866 width: 20px;
3831 3867 height: 20px;
3832 3868 display: block;
3833 3869 float: left;
3834 3870 margin: 0 0 0 3px;
3835 3871 padding: 0;
3836 3872 }
3837 3873
3838 3874 #login div.title h5,#register div.title h5 {
3839 3875 color: #fff;
3840 3876 margin: 10px;
3841 3877 padding: 0;
3842 3878 }
3843 3879
3844 3880 #login div.form div.fields div.field,#register div.form div.fields div.field
3845 3881 {
3846 3882 clear: both;
3847 3883 overflow: hidden;
3848 3884 margin: 0;
3849 3885 padding: 0 0 10px;
3850 3886 }
3851 3887
3852 3888 #login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message
3853 3889 {
3854 3890 height: 1%;
3855 3891 display: block;
3856 3892 color: red;
3857 3893 margin: 8px 0 0;
3858 3894 padding: 0;
3859 3895 max-width: 320px;
3860 3896 }
3861 3897
3862 3898 #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label
3863 3899 {
3864 3900 color: #000;
3865 3901 font-weight: 700;
3866 3902 }
3867 3903
3868 3904 #login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input
3869 3905 {
3870 3906 float: left;
3871 3907 margin: 0;
3872 3908 padding: 0;
3873 3909 }
3874 3910
3875 3911 #login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox
3876 3912 {
3877 3913 margin: 0 0 0 184px;
3878 3914 padding: 0;
3879 3915 }
3880 3916
3881 3917 #login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label
3882 3918 {
3883 3919 color: #565656;
3884 3920 font-weight: 700;
3885 3921 }
3886 3922
3887 3923 #login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input
3888 3924 {
3889 3925 color: #000;
3890 3926 font-size: 1em;
3891 3927 font-weight: 700;
3892 3928 margin: 0;
3893 3929 }
3894 3930
3895 3931 #changeset_content .container .wrapper,#graph_content .container .wrapper
3896 3932 {
3897 3933 width: 600px;
3898 3934 }
3899 3935
3900 3936 #changeset_content .container .left {
3901 3937 float: left;
3902 3938 width: 75%;
3903 3939 padding-left: 5px;
3904 3940 }
3905 3941
3906 3942 #changeset_content .container .left .date,.ac .match {
3907 3943 font-weight: 700;
3908 3944 padding-top: 5px;
3909 3945 padding-bottom: 5px;
3910 3946 }
3911 3947
3912 3948 div#legend_container table td,div#legend_choices table td {
3913 3949 border: none !important;
3914 3950 height: 20px !important;
3915 3951 padding: 0 !important;
3916 3952 }
3917 3953
3918 3954 .q_filter_box {
3919 3955 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
3920 3956 -webkit-border-radius: 4px;
3921 3957 -moz-border-radius: 4px;
3922 3958 border-radius: 4px;
3923 3959 border: 0 none;
3924 3960 color: #AAAAAA;
3925 3961 margin-bottom: -4px;
3926 3962 margin-top: -4px;
3927 3963 padding-left: 3px;
3928 3964 }
3929 3965
3930 3966 #node_filter {
3931 3967 border: 0px solid #545454;
3932 3968 color: #AAAAAA;
3933 3969 padding-left: 3px;
3934 3970 }
3935 3971
3936 3972
3937 3973 .group_members_wrap{
3938 3974 min-height: 85px;
3939 3975 padding-left: 20px;
3940 3976 }
3941 3977
3942 3978 .group_members .group_member{
3943 3979 height: 30px;
3944 3980 padding:0px 0px 0px 0px;
3945 3981 }
3946 3982
3947 3983 .reviewers_member{
3948 3984 height: 15px;
3949 3985 padding:0px 0px 0px 10px;
3950 3986 }
3951 3987
3952 3988 .emails_wrap{
3953 3989 padding: 0px 20px;
3954 3990 }
3955 3991
3956 3992 .emails_wrap .email_entry{
3957 3993 height: 30px;
3958 3994 padding:0px 0px 0px 10px;
3959 3995 }
3960 3996 .emails_wrap .email_entry .email{
3961 3997 float: left
3962 3998 }
3963 3999 .emails_wrap .email_entry .email_action{
3964 4000 float: left
3965 4001 }
3966 4002
3967 4003 /*README STYLE*/
3968 4004
3969 4005 div.readme {
3970 4006 padding:0px;
3971 4007 }
3972 4008
3973 4009 div.readme h2 {
3974 4010 font-weight: normal;
3975 4011 }
3976 4012
3977 4013 div.readme .readme_box {
3978 4014 background-color: #fafafa;
3979 4015 }
3980 4016
3981 4017 div.readme .readme_box {
3982 4018 clear:both;
3983 4019 overflow:hidden;
3984 4020 margin:0;
3985 4021 padding:0 20px 10px;
3986 4022 }
3987 4023
3988 4024 div.readme .readme_box h1, div.readme .readme_box h2, div.readme .readme_box h3, div.readme .readme_box h4, div.readme .readme_box h5, div.readme .readme_box h6 {
3989 4025 border-bottom: 0 !important;
3990 4026 margin: 0 !important;
3991 4027 padding: 0 !important;
3992 4028 line-height: 1.5em !important;
3993 4029 }
3994 4030
3995 4031
3996 4032 div.readme .readme_box h1:first-child {
3997 4033 padding-top: .25em !important;
3998 4034 }
3999 4035
4000 4036 div.readme .readme_box h2, div.readme .readme_box h3 {
4001 4037 margin: 1em 0 !important;
4002 4038 }
4003 4039
4004 4040 div.readme .readme_box h2 {
4005 4041 margin-top: 1.5em !important;
4006 4042 border-top: 4px solid #e0e0e0 !important;
4007 4043 padding-top: .5em !important;
4008 4044 }
4009 4045
4010 4046 div.readme .readme_box p {
4011 4047 color: black !important;
4012 4048 margin: 1em 0 !important;
4013 4049 line-height: 1.5em !important;
4014 4050 }
4015 4051
4016 4052 div.readme .readme_box ul {
4017 4053 list-style: disc !important;
4018 4054 margin: 1em 0 1em 2em !important;
4019 4055 }
4020 4056
4021 4057 div.readme .readme_box ol {
4022 4058 list-style: decimal;
4023 4059 margin: 1em 0 1em 2em !important;
4024 4060 }
4025 4061
4026 4062 div.readme .readme_box pre, code {
4027 4063 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
4028 4064 }
4029 4065
4030 4066 div.readme .readme_box code {
4031 4067 font-size: 12px !important;
4032 4068 background-color: ghostWhite !important;
4033 4069 color: #444 !important;
4034 4070 padding: 0 .2em !important;
4035 4071 border: 1px solid #dedede !important;
4036 4072 }
4037 4073
4038 4074 div.readme .readme_box pre code {
4039 4075 padding: 0 !important;
4040 4076 font-size: 12px !important;
4041 4077 background-color: #eee !important;
4042 4078 border: none !important;
4043 4079 }
4044 4080
4045 4081 div.readme .readme_box pre {
4046 4082 margin: 1em 0;
4047 4083 font-size: 12px;
4048 4084 background-color: #eee;
4049 4085 border: 1px solid #ddd;
4050 4086 padding: 5px;
4051 4087 color: #444;
4052 4088 overflow: auto;
4053 4089 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
4054 4090 -webkit-border-radius: 3px;
4055 4091 -moz-border-radius: 3px;
4056 4092 border-radius: 3px;
4057 4093 }
4058 4094
4059 4095
4060 4096 /** RST STYLE **/
4061 4097
4062 4098
4063 4099 div.rst-block {
4064 4100 padding:0px;
4065 4101 }
4066 4102
4067 4103 div.rst-block h2 {
4068 4104 font-weight: normal;
4069 4105 }
4070 4106
4071 4107 div.rst-block {
4072 4108 background-color: #fafafa;
4073 4109 }
4074 4110
4075 4111 div.rst-block {
4076 4112 clear:both;
4077 4113 overflow:hidden;
4078 4114 margin:0;
4079 4115 padding:0 20px 10px;
4080 4116 }
4081 4117
4082 4118 div.rst-block h1, div.rst-block h2, div.rst-block h3, div.rst-block h4, div.rst-block h5, div.rst-block h6 {
4083 4119 border-bottom: 0 !important;
4084 4120 margin: 0 !important;
4085 4121 padding: 0 !important;
4086 4122 line-height: 1.5em !important;
4087 4123 }
4088 4124
4089 4125
4090 4126 div.rst-block h1:first-child {
4091 4127 padding-top: .25em !important;
4092 4128 }
4093 4129
4094 4130 div.rst-block h2, div.rst-block h3 {
4095 4131 margin: 1em 0 !important;
4096 4132 }
4097 4133
4098 4134 div.rst-block h2 {
4099 4135 margin-top: 1.5em !important;
4100 4136 border-top: 4px solid #e0e0e0 !important;
4101 4137 padding-top: .5em !important;
4102 4138 }
4103 4139
4104 4140 div.rst-block p {
4105 4141 color: black !important;
4106 4142 margin: 1em 0 !important;
4107 4143 line-height: 1.5em !important;
4108 4144 }
4109 4145
4110 4146 div.rst-block ul {
4111 4147 list-style: disc !important;
4112 4148 margin: 1em 0 1em 2em !important;
4113 4149 }
4114 4150
4115 4151 div.rst-block ol {
4116 4152 list-style: decimal;
4117 4153 margin: 1em 0 1em 2em !important;
4118 4154 }
4119 4155
4120 4156 div.rst-block pre, code {
4121 4157 font: 12px "Bitstream Vera Sans Mono","Courier",monospace;
4122 4158 }
4123 4159
4124 4160 div.rst-block code {
4125 4161 font-size: 12px !important;
4126 4162 background-color: ghostWhite !important;
4127 4163 color: #444 !important;
4128 4164 padding: 0 .2em !important;
4129 4165 border: 1px solid #dedede !important;
4130 4166 }
4131 4167
4132 4168 div.rst-block pre code {
4133 4169 padding: 0 !important;
4134 4170 font-size: 12px !important;
4135 4171 background-color: #eee !important;
4136 4172 border: none !important;
4137 4173 }
4138 4174
4139 4175 div.rst-block pre {
4140 4176 margin: 1em 0;
4141 4177 font-size: 12px;
4142 4178 background-color: #eee;
4143 4179 border: 1px solid #ddd;
4144 4180 padding: 5px;
4145 4181 color: #444;
4146 4182 overflow: auto;
4147 4183 -webkit-box-shadow: rgba(0,0,0,0.07) 0 1px 2px inset;
4148 4184 -webkit-border-radius: 3px;
4149 4185 -moz-border-radius: 3px;
4150 4186 border-radius: 3px;
4151 4187 }
4152 4188
4153 4189
4154 4190 /** comment main **/
4155 4191 .comments {
4156 4192 padding:10px 20px;
4157 4193 }
4158 4194
4159 4195 .comments .comment {
4160 4196 border: 1px solid #ddd;
4161 4197 margin-top: 10px;
4162 4198 -webkit-border-radius: 4px;
4163 4199 -moz-border-radius: 4px;
4164 4200 border-radius: 4px;
4165 4201 }
4166 4202
4167 4203 .comments .comment .meta {
4168 4204 background: #f8f8f8;
4169 4205 padding: 4px;
4170 4206 border-bottom: 1px solid #ddd;
4171 4207 height: 18px;
4172 4208 }
4173 4209
4174 4210 .comments .comment .meta img {
4175 4211 vertical-align: middle;
4176 4212 }
4177 4213
4178 4214 .comments .comment .meta .user {
4179 4215 font-weight: bold;
4180 4216 float: left;
4181 4217 padding: 4px 2px 2px 2px;
4182 4218 }
4183 4219
4184 4220 .comments .comment .meta .date {
4185 4221 float: left;
4186 4222 padding:4px 4px 0px 4px;
4187 4223 }
4188 4224
4189 4225 .comments .comment .text {
4190 4226 background-color: #FAFAFA;
4191 4227 }
4192 4228 .comment .text div.rst-block p {
4193 4229 margin: 0.5em 0px !important;
4194 4230 }
4195 4231
4196 4232 .comments .comments-number{
4197 4233 padding:0px 0px 10px 0px;
4198 4234 font-weight: bold;
4199 4235 color: #666;
4200 4236 font-size: 16px;
4201 4237 }
4202 4238
4203 4239 /** comment form **/
4204 4240
4205 4241 .status-block{
4206 4242 height:80px;
4207 4243 clear:both
4208 4244 }
4209 4245
4210 4246 .comment-form .clearfix{
4211 4247 background: #EEE;
4212 4248 -webkit-border-radius: 4px;
4213 4249 -moz-border-radius: 4px;
4214 4250 border-radius: 4px;
4215 4251 padding: 10px;
4216 4252 }
4217 4253
4218 4254 div.comment-form {
4219 4255 margin-top: 20px;
4220 4256 }
4221 4257
4222 4258 .comment-form strong {
4223 4259 display: block;
4224 4260 margin-bottom: 15px;
4225 4261 }
4226 4262
4227 4263 .comment-form textarea {
4228 4264 width: 100%;
4229 4265 height: 100px;
4230 4266 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
4231 4267 }
4232 4268
4233 4269 form.comment-form {
4234 4270 margin-top: 10px;
4235 4271 margin-left: 10px;
4236 4272 }
4237 4273
4238 4274 .comment-form-submit {
4239 4275 margin-top: 5px;
4240 4276 margin-left: 525px;
4241 4277 }
4242 4278
4243 4279 .file-comments {
4244 4280 display: none;
4245 4281 }
4246 4282
4247 4283 .comment-form .comment {
4248 4284 margin-left: 10px;
4249 4285 }
4250 4286
4251 4287 .comment-form .comment-help{
4252 4288 padding: 0px 0px 5px 0px;
4253 4289 color: #666;
4254 4290 }
4255 4291
4256 4292 .comment-form .comment-button{
4257 4293 padding-top:5px;
4258 4294 }
4259 4295
4260 4296 .add-another-button {
4261 4297 margin-left: 10px;
4262 4298 margin-top: 10px;
4263 4299 margin-bottom: 10px;
4264 4300 }
4265 4301
4266 4302 .comment .buttons {
4267 4303 float: right;
4268 4304 padding:2px 2px 0px 0px;
4269 4305 }
4270 4306
4271 4307
4272 4308 .show-inline-comments{
4273 4309 position: relative;
4274 4310 top:1px
4275 4311 }
4276 4312
4277 4313 /** comment inline form **/
4278 4314 .comment-inline-form .overlay{
4279 4315 display: none;
4280 4316 }
4281 4317 .comment-inline-form .overlay.submitting{
4282 4318 display:block;
4283 4319 background: none repeat scroll 0 0 white;
4284 4320 font-size: 16px;
4285 4321 opacity: 0.5;
4286 4322 position: absolute;
4287 4323 text-align: center;
4288 4324 vertical-align: top;
4289 4325
4290 4326 }
4291 4327 .comment-inline-form .overlay.submitting .overlay-text{
4292 4328 width:100%;
4293 4329 margin-top:5%;
4294 4330 }
4295 4331
4296 4332 .comment-inline-form .clearfix{
4297 4333 background: #EEE;
4298 4334 -webkit-border-radius: 4px;
4299 4335 -moz-border-radius: 4px;
4300 4336 border-radius: 4px;
4301 4337 padding: 5px;
4302 4338 }
4303 4339
4304 4340 div.comment-inline-form {
4305 4341 padding:4px 0px 6px 0px;
4306 4342 }
4307 4343
4308 4344
4309 4345 tr.hl-comment{
4310 4346 /*
4311 4347 background-color: #FFFFCC !important;
4312 4348 */
4313 4349 }
4314 4350
4315 4351 /*
4316 4352 tr.hl-comment pre {
4317 4353 border-top: 2px solid #FFEE33;
4318 4354 border-left: 2px solid #FFEE33;
4319 4355 border-right: 2px solid #FFEE33;
4320 4356 }
4321 4357 */
4322 4358
4323 4359 .comment-inline-form strong {
4324 4360 display: block;
4325 4361 margin-bottom: 15px;
4326 4362 }
4327 4363
4328 4364 .comment-inline-form textarea {
4329 4365 width: 100%;
4330 4366 height: 100px;
4331 4367 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
4332 4368 }
4333 4369
4334 4370 form.comment-inline-form {
4335 4371 margin-top: 10px;
4336 4372 margin-left: 10px;
4337 4373 }
4338 4374
4339 4375 .comment-inline-form-submit {
4340 4376 margin-top: 5px;
4341 4377 margin-left: 525px;
4342 4378 }
4343 4379
4344 4380 .file-comments {
4345 4381 display: none;
4346 4382 }
4347 4383
4348 4384 .comment-inline-form .comment {
4349 4385 margin-left: 10px;
4350 4386 }
4351 4387
4352 4388 .comment-inline-form .comment-help{
4353 4389 padding: 0px 0px 2px 0px;
4354 4390 color: #666666;
4355 4391 font-size: 10px;
4356 4392 }
4357 4393
4358 4394 .comment-inline-form .comment-button{
4359 4395 padding-top:5px;
4360 4396 }
4361 4397
4362 4398 /** comment inline **/
4363 4399 .inline-comments {
4364 4400 padding:10px 20px;
4365 4401 }
4366 4402
4367 4403 .inline-comments div.rst-block {
4368 4404 clear:both;
4369 4405 overflow:hidden;
4370 4406 margin:0;
4371 4407 padding:0 20px 0px;
4372 4408 }
4373 4409 .inline-comments .comment {
4374 4410 border: 1px solid #ddd;
4375 4411 -webkit-border-radius: 4px;
4376 4412 -moz-border-radius: 4px;
4377 4413 border-radius: 4px;
4378 4414 margin: 3px 3px 5px 5px;
4379 4415 background-color: #FAFAFA;
4380 4416 }
4381 4417 .inline-comments .add-comment {
4382 4418 padding: 2px 4px 8px 5px;
4383 4419 }
4384 4420
4385 4421 .inline-comments .comment-wrapp{
4386 4422 padding:1px;
4387 4423 }
4388 4424 .inline-comments .comment .meta {
4389 4425 background: #f8f8f8;
4390 4426 padding: 4px;
4391 4427 border-bottom: 1px solid #ddd;
4392 4428 height: 20px;
4393 4429 }
4394 4430
4395 4431 .inline-comments .comment .meta img {
4396 4432 vertical-align: middle;
4397 4433 }
4398 4434
4399 4435 .inline-comments .comment .meta .user {
4400 4436 font-weight: bold;
4401 4437 float:left;
4402 4438 padding: 3px;
4403 4439 }
4404 4440
4405 4441 .inline-comments .comment .meta .date {
4406 4442 float:left;
4407 4443 padding: 3px;
4408 4444 }
4409 4445
4410 4446 .inline-comments .comment .text {
4411 4447 background-color: #FAFAFA;
4412 4448 }
4413 4449
4414 4450 .inline-comments .comments-number{
4415 4451 padding:0px 0px 10px 0px;
4416 4452 font-weight: bold;
4417 4453 color: #666;
4418 4454 font-size: 16px;
4419 4455 }
4420 4456 .inline-comments-button .add-comment{
4421 4457 margin:2px 0px 8px 5px !important
4422 4458 }
4423 4459
4424 4460
4425 4461 .notification-paginator{
4426 4462 padding: 0px 0px 4px 16px;
4427 4463 float: left;
4428 4464 }
4429 4465
4430 4466 .notifications{
4431 4467 border-radius: 4px 4px 4px 4px;
4432 4468 -webkit-border-radius: 4px;
4433 4469 -moz-border-radius: 4px;
4434 4470 float: right;
4435 4471 margin: 20px 0px 0px 0px;
4436 4472 position: absolute;
4437 4473 text-align: center;
4438 4474 width: 26px;
4439 4475 z-index: 1000;
4440 4476 }
4441 4477 .notifications a{
4442 4478 color:#888 !important;
4443 4479 display: block;
4444 4480 font-size: 10px;
4445 4481 background-color: #DEDEDE !important;
4446 4482 border-radius: 2px !important;
4447 4483 -webkit-border-radius: 2px !important;
4448 4484 -moz-border-radius: 2px !important;
4449 4485 }
4450 4486 .notifications a:hover{
4451 4487 text-decoration: none !important;
4452 4488 background-color: #EEEFFF !important;
4453 4489 }
4454 4490 .notification-header{
4455 4491 padding-top:6px;
4456 4492 }
4457 4493 .notification-header .desc{
4458 4494 font-size: 16px;
4459 4495 height: 24px;
4460 4496 float: left
4461 4497 }
4462 4498 .notification-list .container.unread{
4463 4499 background: none repeat scroll 0 0 rgba(255, 255, 180, 0.6);
4464 4500 }
4465 4501 .notification-header .gravatar{
4466 4502 background: none repeat scroll 0 0 transparent;
4467 4503 padding: 0px 0px 0px 8px;
4468 4504 }
4469 4505 .notification-list .container .notification-header .desc{
4470 4506 font-weight: bold;
4471 4507 font-size: 17px;
4472 4508 }
4473 4509 .notification-table{
4474 4510 border: 1px solid #ccc;
4475 4511 -webkit-border-radius: 6px 6px 6px 6px;
4476 4512 -moz-border-radius: 6px 6px 6px 6px;
4477 4513 border-radius: 6px 6px 6px 6px;
4478 4514 clear: both;
4479 4515 margin: 0px 20px 0px 20px;
4480 4516 }
4481 4517 .notification-header .delete-notifications{
4482 4518 float: right;
4483 4519 padding-top: 8px;
4484 4520 cursor: pointer;
4485 4521 }
4486 4522 .notification-header .read-notifications{
4487 4523 float: right;
4488 4524 padding-top: 8px;
4489 4525 cursor: pointer;
4490 4526 }
4491 4527 .notification-subject{
4492 4528 clear:both;
4493 4529 border-bottom: 1px solid #eee;
4494 4530 padding:5px 0px 5px 38px;
4495 4531 }
4496 4532
4497 4533 .notification-body{
4498 4534 clear:both;
4499 4535 margin: 34px 2px 2px 8px
4500 4536 }
4501 4537
4502 4538 /****
4503 4539 PULL REQUESTS
4504 4540 *****/
4505 4541 .pullrequests_section_head {
4506 4542 padding:10px 10px 10px 0px;
4507 4543 font-size:16px;
4508 4544 font-weight: bold;
4509 4545 }
4510 4546
4511 4547 /****
4512 4548 PERMS
4513 4549 *****/
4514 4550 #perms .perms_section_head {
4515 4551 padding:10px 10px 10px 0px;
4516 4552 font-size:16px;
4517 4553 font-weight: bold;
4518 4554 }
4519 4555
4520 4556 #perms .perm_tag{
4521 4557 padding: 1px 3px 1px 3px;
4522 4558 font-size: 10px;
4523 4559 font-weight: bold;
4524 4560 text-transform: uppercase;
4525 4561 white-space: nowrap;
4526 4562 -webkit-border-radius: 3px;
4527 4563 -moz-border-radius: 3px;
4528 4564 border-radius: 3px;
4529 4565 }
4530 4566
4531 4567 #perms .perm_tag.admin{
4532 4568 background-color: #B94A48;
4533 4569 color: #ffffff;
4534 4570 }
4535 4571
4536 4572 #perms .perm_tag.write{
4537 4573 background-color: #B94A48;
4538 4574 color: #ffffff;
4539 4575 }
4540 4576
4541 4577 #perms .perm_tag.read{
4542 4578 background-color: #468847;
4543 4579 color: #ffffff;
4544 4580 }
4545 4581
4546 4582 #perms .perm_tag.none{
4547 4583 background-color: #bfbfbf;
4548 4584 color: #ffffff;
4549 4585 }
4550 4586
4551 4587 .perm-gravatar{
4552 4588 vertical-align:middle;
4553 4589 padding:2px;
4554 4590 }
4555 4591 .perm-gravatar-ac{
4556 4592 vertical-align:middle;
4557 4593 padding:2px;
4558 4594 width: 14px;
4559 4595 height: 14px;
4560 4596 }
4561 4597
4562 4598 /*****************************************************************************
4563 4599 DIFFS CSS
4564 4600 ******************************************************************************/
4565 4601
4566 4602 div.diffblock {
4567 4603 overflow: auto;
4568 4604 padding: 0px;
4569 4605 border: 1px solid #ccc;
4570 4606 background: #f8f8f8;
4571 4607 font-size: 100%;
4572 4608 line-height: 100%;
4573 4609 /* new */
4574 4610 line-height: 125%;
4575 4611 -webkit-border-radius: 6px 6px 0px 0px;
4576 4612 -moz-border-radius: 6px 6px 0px 0px;
4577 4613 border-radius: 6px 6px 0px 0px;
4578 4614 }
4579 4615 div.diffblock.margined{
4580 4616 margin: 0px 20px 0px 20px;
4581 4617 }
4582 4618 div.diffblock .code-header{
4583 4619 border-bottom: 1px solid #CCCCCC;
4584 4620 background: #EEEEEE;
4585 4621 padding:10px 0 10px 0;
4586 4622 height: 14px;
4587 4623 }
4588 4624 div.diffblock .code-header.cv{
4589 4625 height: 34px;
4590 4626 }
4591 4627 div.diffblock .code-header-title{
4592 4628 padding: 0px 0px 10px 5px !important;
4593 4629 margin: 0 !important;
4594 4630 }
4595 4631 div.diffblock .code-header .hash{
4596 4632 float: left;
4597 4633 padding: 2px 0 0 2px;
4598 4634 }
4599 4635 div.diffblock .code-header .date{
4600 4636 float:left;
4601 4637 text-transform: uppercase;
4602 4638 padding: 2px 0px 0px 2px;
4603 4639 }
4604 4640 div.diffblock .code-header div{
4605 4641 margin-left:4px;
4606 4642 font-weight: bold;
4607 4643 font-size: 14px;
4608 4644 }
4609 4645 div.diffblock .code-body{
4610 4646 background: #FFFFFF;
4611 4647 }
4612 4648 div.diffblock pre.raw{
4613 4649 background: #FFFFFF;
4614 4650 color:#000000;
4615 4651 }
4616 4652 table.code-difftable{
4617 4653 border-collapse: collapse;
4618 4654 width: 99%;
4619 4655 }
4620 4656 table.code-difftable td {
4621 4657 padding: 0 !important;
4622 4658 background: none !important;
4623 4659 border:0 !important;
4624 4660 vertical-align: none !important;
4625 4661 }
4626 4662 table.code-difftable .context{
4627 4663 background:none repeat scroll 0 0 #DDE7EF;
4628 4664 }
4629 4665 table.code-difftable .add{
4630 4666 background:none repeat scroll 0 0 #DDFFDD;
4631 4667 }
4632 4668 table.code-difftable .add ins{
4633 4669 background:none repeat scroll 0 0 #AAFFAA;
4634 4670 text-decoration:none;
4635 4671 }
4636 4672 table.code-difftable .del{
4637 4673 background:none repeat scroll 0 0 #FFDDDD;
4638 4674 }
4639 4675 table.code-difftable .del del{
4640 4676 background:none repeat scroll 0 0 #FFAAAA;
4641 4677 text-decoration:none;
4642 4678 }
4643 4679
4644 4680 /** LINE NUMBERS **/
4645 4681 table.code-difftable .lineno{
4646 4682
4647 4683 padding-left:2px;
4648 4684 padding-right:2px;
4649 4685 text-align:right;
4650 4686 width:32px;
4651 4687 -moz-user-select:none;
4652 4688 -webkit-user-select: none;
4653 4689 border-right: 1px solid #CCC !important;
4654 4690 border-left: 0px solid #CCC !important;
4655 4691 border-top: 0px solid #CCC !important;
4656 4692 border-bottom: none !important;
4657 4693 vertical-align: middle !important;
4658 4694
4659 4695 }
4660 4696 table.code-difftable .lineno.new {
4661 4697 }
4662 4698 table.code-difftable .lineno.old {
4663 4699 }
4664 4700 table.code-difftable .lineno a{
4665 4701 color:#747474 !important;
4666 4702 font:11px "Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace !important;
4667 4703 letter-spacing:-1px;
4668 4704 text-align:right;
4669 4705 padding-right: 2px;
4670 4706 cursor: pointer;
4671 4707 display: block;
4672 4708 width: 32px;
4673 4709 }
4674 4710
4675 4711 table.code-difftable .lineno-inline{
4676 4712 background:none repeat scroll 0 0 #FFF !important;
4677 4713 padding-left:2px;
4678 4714 padding-right:2px;
4679 4715 text-align:right;
4680 4716 width:30px;
4681 4717 -moz-user-select:none;
4682 4718 -webkit-user-select: none;
4683 4719 }
4684 4720
4685 4721 /** CODE **/
4686 4722 table.code-difftable .code {
4687 4723 display: block;
4688 4724 width: 100%;
4689 4725 }
4690 4726 table.code-difftable .code td{
4691 4727 margin:0;
4692 4728 padding:0;
4693 4729 }
4694 4730 table.code-difftable .code pre{
4695 4731 margin:0;
4696 4732 padding:0;
4697 4733 height: 17px;
4698 4734 line-height: 17px;
4699 4735 }
4700 4736
4701 4737
4702 4738 .diffblock.margined.comm .line .code:hover{
4703 4739 background-color:#FFFFCC !important;
4704 4740 cursor: pointer !important;
4705 4741 background-image:url("../images/icons/comment_add.png") !important;
4706 4742 background-repeat:no-repeat !important;
4707 4743 background-position: right !important;
4708 4744 background-position: 0% 50% !important;
4709 4745 }
4710 4746 .diffblock.margined.comm .line .code.no-comment:hover{
4711 4747 background-image: none !important;
4712 4748 cursor: auto !important;
4713 4749 background-color: inherit !important;
4714 4750
4715 4751 }
@@ -1,185 +1,190 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.html"/>
4 4
5 5 <%def name="title()">
6 6 ${_('%s Changeset') % c.repo_name} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
7 7 </%def>
8 8
9 9 <%def name="breadcrumbs_links()">
10 10 ${h.link_to(_(u'Home'),h.url('/'))}
11 11 &raquo;
12 12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 13 &raquo;
14 14 ${_('Changeset')} - <span class='hash'>r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}</span>
15 15 </%def>
16 16
17 17 <%def name="page_nav()">
18 18 ${self.menu('changelog')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <div class="box">
23 23 <!-- box / title -->
24 24 <div class="title">
25 25 ${self.breadcrumbs()}
26 26 </div>
27 <script>
28 var _USERS_AC_DATA = ${c.users_array|n};
29 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
30 AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
31 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
32 </script>
27 33 <div class="table">
28 34 <div class="diffblock">
29 35 <div class="code-header">
30 36 <div class="hash">
31 37 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
32 38 </div>
33 39 <div class="date">
34 40 ${h.fmt_date(c.changeset.date)}
35 41 </div>
36 42 <div class="changeset-status-container">
37 43 %if c.statuses:
38 44 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</div>
39 45 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[0])}" /></div>
40 46 %endif
41 47 </div>
42 48 <div class="diff-actions">
43 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
49 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='raw')}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
44 50 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('download diff'))}"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
45 51 ${c.ignorews_url(request.GET)}
46 52 ${c.context_url(request.GET)}
47 53 </div>
48 54 <div class="comments-number" style="float:right;padding-right:5px">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
49 55 </div>
50 56 </div>
51 57 <div id="changeset_content">
52 58 <div class="container">
53 59 <div class="left">
54 60 <div class="author">
55 61 <div class="gravatar">
56 62 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.changeset.author),20)}"/>
57 63 </div>
58 64 <span>${h.person(c.changeset.author)}</span><br/>
59 65 <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
60 66 </div>
61 67 <div class="message">${h.urlify_commit(c.changeset.message, c.repo_name)}</div>
62 68 </div>
63 69 <div class="right">
64 70 <div class="changes">
65 71 % if len(c.changeset.affected_files) <= c.affected_files_cut_off:
66 72 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
67 73 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
68 74 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
69 75 % else:
70 76 <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
71 77 <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
72 78 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
73 79 % endif
74 80 </div>
75 81
76 82 %if c.changeset.parents:
77 83 %for p_cs in reversed(c.changeset.parents):
78 84 <div class="parent">${_('Parent')}
79 85 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
80 86 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
81 87 </div>
82 88 %endfor
83 89 %else:
84 90 <div class="parent">${_('No parents')}</div>
85 91 %endif
86 92 <span class="logtags">
87 93 %if len(c.changeset.parents)>1:
88 94 <span class="merge">${_('merge')}</span>
89 95 %endif
90 96 %if c.changeset.branch:
91 97 <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
92 98 ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
93 99 </span>
94 100 %endif
95 101 %for tag in c.changeset.tags:
96 102 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
97 103 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
98 104 %endfor
99 105 </span>
100 106 </div>
101 107 </div>
102 108 <span>
109 % if c.limited_diff:
110 ${_('%s files affected:') % (len(c.changeset.affected_files))}
111 % else:
103 112 ${_('%s files affected with %s insertions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
113 %endif
104 114 </span>
105 115 <div class="cs_files">
106 %for change,filenode,diff,cs1,cs2,stat in c.changes:
116 %for FID, (cs1, cs2, change, path, diff, stats) in c.changes[c.changeset.raw_id].iteritems():
107 117 <div class="cs_${change}">
108 118 <div class="node">
109 %if change != 'removed':
110 ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path,request.GET)+"_target")}
111 %else:
112 ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
113 %endif
119 <a href="#${FID}">${h.safe_unicode(path)}</a>
114 120 </div>
115 <div class="changes">${h.fancy_file_stats(stat)}</div>
121 <div class="changes">${h.fancy_file_stats(stats)}</div>
116 122 </div>
117 123 %endfor
118 % if c.cut_off:
119 ${_('Changeset was too big and was cut off...')}
124 % if c.limited_diff:
125 <h5>${_('Changeset was too big and was cut off...')}</h5>
120 126 % endif
121 127 </div>
122 128 </div>
123 129
124 130 </div>
125 <script>
126 var _USERS_AC_DATA = ${c.users_array|n};
127 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
128 AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
129 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
130 </script>
131
131 132 ## diff block
132 133 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
133 ${diff_block.diff_block(c.changes)}
134 ${diff_block.diff_block(c.changes[c.changeset.raw_id])}
135
136 % if c.limited_diff:
137 <h4>${_('Changeset was too big and was cut off...')}</h4>
138 % endif
134 139
135 140 ## template for inline comment form
136 141 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
137 142 ${comment.comment_inline_form()}
138 143
139 144 ## render comments and inlines
140 145 ${comment.generate_comments()}
141 146
142 147 ## main comment form and it status
143 148 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id),
144 149 h.changeset_status(c.rhodecode_db_repo, c.changeset.raw_id))}
145 150
146 151 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
147 152 <script type="text/javascript">
148 153 YUE.onDOMReady(function(){
149 154 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
150 155 var show = 'none';
151 156 var target = e.currentTarget;
152 157 if(target == null){
153 158 target = this;
154 159 }
155 160 if(target.checked){
156 161 var show = ''
157 162 }
158 163 var boxid = YUD.getAttribute(target,'id_for');
159 164 var comments = YUQ('#{0} .inline-comments'.format(boxid));
160 165 for(c in comments){
161 166 YUD.setStyle(comments[c],'display',show);
162 167 }
163 168 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
164 169 for(c in btns){
165 170 YUD.setStyle(btns[c],'display',show);
166 171 }
167 172 })
168 173
169 174 YUE.on(YUQ('.line'),'click',function(e){
170 175 var tr = e.currentTarget;
171 176 if(tr == null){
172 177 tr = this;
173 178 }
174 179 injectInlineForm(tr);
175 180 });
176 181
177 182 // inject comments into they proper positions
178 183 var file_comments = YUQ('.inline-comment-placeholder');
179 184 renderInlineComments(file_comments);
180 185 })
181 186
182 187 </script>
183 188
184 189 </div>
185 190 </%def>
@@ -1,122 +1,122 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="/base/base.html"/>
3 3
4 4 <%def name="title()">
5 5 ${_('%s Changesets') % c.repo_name} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)} - ${c.rhodecode_name}
6 6 </%def>
7 7
8 8 <%def name="breadcrumbs_links()">
9 9 ${h.link_to(_(u'Home'),h.url('/'))}
10 10 &raquo;
11 11 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 12 &raquo;
13 13 ${_('Changesets')} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
14 14 </%def>
15 15
16 16 <%def name="page_nav()">
17 17 ${self.menu('changelog')}
18 18 </%def>
19 19
20 20 <%def name="main()">
21 21 <div class="box">
22 22 <!-- box / title -->
23 23 <div class="title">
24 24 ${self.breadcrumbs()}
25 25 </div>
26 26 <div class="table">
27 27 <div id="body" class="diffblock">
28 28 <div class="code-header cv">
29 29 <h3 class="code-header-title">${_('Compare View')}</h3>
30 30 <div>
31 31 ${_('Changesets')} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
32 32 </div>
33 33 </div>
34 34 </div>
35 35 <div id="changeset_compare_view_content">
36 36 <div class="container">
37 37 <table class="compare_view_commits noborder">
38 38 %for cnt,cs in enumerate(c.cs_ranges):
39 39 <tr>
40 40 <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),14)}"/></div></td>
41 41 <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</td>
42 42 <td><div class="author">${h.person(cs.author)}</div></td>
43 43 <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
44 44 <td>
45 45 %if c.statuses:
46 46 <div title="${h.tooltip(_('Changeset status'))}" class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[cnt])}" /></div>
47 47 %endif
48 48 </td>
49 49 <td><div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name)}</div></td>
50 50 </tr>
51 51 %endfor
52 52 </table>
53 53 </div>
54 54 <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
55 55 <div class="cs_files">
56 56 %for cs in c.cs_ranges:
57 57 <div class="cur_cs">${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
58 %for change,filenode,diff,cs1,cs2,st in c.changes[cs.raw_id]:
59 <div class="cs_${change}">${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID(cs.raw_id,filenode.path)))}</div>
58 %for FID, (cs1, cs2, change, path, diff, stats) in c.changes[cs.raw_id].iteritems():
59 <div class="cs_${change}">${h.link_to(h.safe_unicode(path),h.url.current(anchor=FID))}</div>
60 60 %endfor
61 61 %endfor
62 62 </div>
63 63 </div>
64 64
65 65 </div>
66 66 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
67 67 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
68 68 %for cs in c.cs_ranges:
69 69 ##${comment.comment_inline_form(cs)}
70 70 ## diff block
71 71 <div class="h3">
72 72 <a class="tooltip" title="${h.tooltip(cs.message)}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}">${'r%s:%s' % (cs.revision,h.short_id(cs.raw_id))}</a>
73 73 <div class="gravatar">
74 74 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),20)}"/>
75 75 </div>
76 76 <div class="right">
77 77 <span class="logtags">
78 78 %if len(cs.parents)>1:
79 79 <span class="merge">${_('merge')}</span>
80 80 %endif
81 81 %if cs.branch:
82 82 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
83 83 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
84 84 </span>
85 85 %endif
86 86 %if h.is_hg(c.rhodecode_repo):
87 87 %for book in cs.bookmarks:
88 88 <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
89 89 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
90 90 </span>
91 91 %endfor
92 92 %endif
93 93 %for tag in cs.tags:
94 94 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
95 95 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
96 96 %endfor
97 97 </span>
98 98 </div>
99 99 </div>
100 100 ${diff_block.diff_block(c.changes[cs.raw_id])}
101 101 ##${comment.comments(cs)}
102 102
103 103 %endfor
104 104 <script type="text/javascript">
105 105
106 106 YUE.onDOMReady(function(){
107 107
108 108 YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
109 109 var act = e.currentTarget.nextElementSibling;
110 110
111 111 if(YUD.hasClass(act,'active')){
112 112 YUD.removeClass(act,'active');
113 113 YUD.setStyle(act,'display','none');
114 114 }else{
115 115 YUD.addClass(act,'active');
116 116 YUD.setStyle(act,'display','');
117 117 }
118 118 });
119 119 })
120 120 </script>
121 121 </div>
122 122 </%def>
@@ -1,61 +1,61 b''
1 1 ## -*- coding: utf-8 -*-
2 2 ##usage:
3 3 ## <%namespace name="diff_block" file="/changeset/diff_block.html"/>
4 4 ## ${diff_block.diff_block(change)}
5 5 ##
6 6 <%def name="diff_block(change)">
7 7
8 %for op,filenode,diff,cs1,cs2,stat in change:
9 %if op !='removed':
10 <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}_target" style="clear:both;margin-top:25px"></div>
11 <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}" class="diffblock margined comm">
8 %for FID,(cs1, cs2, change, path, diff, stats) in change.iteritems():
9 ##%if op !='removed':
10 <div id="${FID}_target" style="clear:both;margin-top:25px"></div>
11 <div id="${FID}" class="diffblock margined comm">
12 12 <div class="code-header">
13 13 <div class="changeset_header">
14 14 <div class="changeset_file">
15 ${h.link_to_if(change!='removed',h.safe_unicode(filenode.path),h.url('files_home',repo_name=c.repo_name,
16 revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
15 ${h.link_to_if(change!='removed',h.safe_unicode(path),h.url('files_home',repo_name=c.repo_name,
16 revision=cs2,f_path=h.safe_unicode(path)))}
17 17 </div>
18 18 <div class="diff-actions">
19 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('diff'))}"><img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/></a>
20 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw')}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
21 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download')}" class="tooltip" title="${h.tooltip(_('download diff'))}"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
22 ${c.ignorews_url(request.GET, h.FID(filenode.changeset.raw_id,filenode.path))}
23 ${c.context_url(request.GET, h.FID(filenode.changeset.raw_id,filenode.path))}
19 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('diff'))}"><img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/></a>
20 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='raw')}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
21 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='download')}" class="tooltip" title="${h.tooltip(_('download diff'))}"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
22 ${c.ignorews_url(request.GET, h.FID(cs2,path))}
23 ${c.context_url(request.GET, h.FID(cs2,path))}
24 24 </div>
25 25 <span style="float:right;margin-top:-3px">
26 26 <label>
27 27 ${_('show inline comments')}
28 ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(filenode.changeset.raw_id,filenode.path))}
28 ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(cs2,path))}
29 29 </label>
30 30 </span>
31 31 </div>
32 32 </div>
33 33 <div class="code-body">
34 <div class="full_f_path" path="${h.safe_unicode(filenode.path)}"></div>
34 <div class="full_f_path" path="${h.safe_unicode(path)}"></div>
35 35 ${diff|n}
36 36 </div>
37 37 </div>
38 %endif
38 ##%endif
39 39 %endfor
40 40
41 41 </%def>
42 42
43 43 <%def name="diff_block_simple(change)">
44 44
45 45 %for op,filenode_path,diff in change:
46 46 <div id="${h.FID('',filenode_path)}_target" style="clear:both;margin-top:25px"></div>
47 47 <div id="${h.FID('',filenode_path)}" class="diffblock margined comm">
48 48 <div class="code-header">
49 49 <div class="changeset_header">
50 50 <div class="changeset_file">
51 51 <a href="#">${h.safe_unicode(filenode_path)}</a>
52 52 </div>
53 53 </div>
54 54 </div>
55 55 <div class="code-body">
56 56 <div class="full_f_path" path="${h.safe_unicode(filenode_path)}"></div>
57 57 ${diff|n}
58 58 </div>
59 59 </div>
60 60 %endfor
61 61 </%def>
@@ -1,9 +1,11 b''
1 %if h.is_hg(c.scm_type):
2 # ${c.scm_type.upper()} changeset patch
1 %if h.is_hg(c.rhodecode_repo):
2 # ${c.rhodecode_repo.alias.upper()} changeset patch
3 3 # User ${c.changeset.author|n}
4 4 # Date ${c.changeset.date}
5 5 # Node ID ${c.changeset.raw_id}
6 6 ${c.parent_tmpl}
7 7 ${c.changeset.message}
8 8 %endif
9 ${c.diffs|n}
9 %for FID, (cs1, cs2, change, path, diff, stats) in c.changes[c.changeset.raw_id].iteritems():
10 ${diff|n}
11 %endfor
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now