Changeset - 22333ddd1a40
[Not reviewed]
beta
0 4 0
Marcin Kuzminski - 14 years ago 2011-12-10 23:51:53
marcin@python-works.com
implements #307, configurable diffs
- global context increase and whitespace
- perfile context and whitespace
- fixed anchor links support
4 files changed with 161 insertions and 43 deletions:
0 comments (0 inline, 0 general)
rhodecode/controllers/changeset.py
Show inline comments
 
@@ -16,68 +16,161 @@
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
import logging
 
import traceback
 
from collections import defaultdict
 
from webob.exc import HTTPForbidden
 

	
 
from pylons import tmpl_context as c, url, request, response
 
from pylons.i18n.translation import _
 
from pylons.controllers.util import redirect
 
from pylons.decorators import jsonify
 

	
 
from vcs.exceptions import RepositoryError, ChangesetError, \
 
    ChangesetDoesNotExistError
 
from vcs.nodes import FileNode
 

	
 
import rhodecode.lib.helpers as h
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from rhodecode.lib.base import BaseRepoController, render
 
from rhodecode.lib.utils import EmptyChangeset
 
from rhodecode.lib.compat import OrderedDict
 
from rhodecode.lib import diffs
 
from rhodecode.model.db import ChangesetComment
 
from rhodecode.model.comment import ChangesetCommentsModel
 

	
 
from vcs.exceptions import RepositoryError, ChangesetError, \
 
    ChangesetDoesNotExistError
 
from vcs.nodes import FileNode
 
from webob.exc import HTTPForbidden
 
from rhodecode.model.meta import Session
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def anchor_url(revision,path):
 
    fid = h.FID(revision, path)
 
    return h.url.current(anchor=fid,**request.GET)
 

	
 
def get_ignore_ws(fid, GET):
 
    ig_ws_global = request.GET.get('ignorews')
 
    ig_ws = filter(lambda k:k.startswith('WS'),GET.getall(fid))
 
    if ig_ws:
 
        try:
 
            return int(ig_ws[0].split(':')[-1])
 
        except:
 
            pass
 
    return ig_ws_global
 

	
 
def _ignorews_url(fileid=None):
 

	
 
    params = defaultdict(list)
 
    lbl = _('show white space')
 
    ig_ws = get_ignore_ws(fileid, request.GET)
 
    ln_ctx = get_line_ctx(fileid, request.GET)
 
    # global option
 
    if fileid is None:
 
        if ig_ws is None:
 
            params['ignorews'] += [1]
 
            lbl = _('ignore white space')
 
        ctx_key = 'context'
 
        ctx_val = ln_ctx
 
    # per file options
 
    else:
 
        if ig_ws is None:
 
            params[fileid] += ['WS:1']
 
            lbl = _('ignore white space')
 

	
 
        ctx_key = fileid
 
        ctx_val = 'C:%s' % ln_ctx
 
    # if we have passed in ln_ctx pass it along to our params
 
    if ln_ctx:
 
        params[ctx_key] += [ctx_val]
 
    
 
    params['anchor'] = fileid
 
    return h.link_to(lbl, h.url.current(**params))
 

	
 
def get_line_ctx(fid, GET):
 
    ln_ctx_global = request.GET.get('context')
 
    ln_ctx = filter(lambda k:k.startswith('C'),GET.getall(fid))
 
    
 
    if ln_ctx:
 
        retval = ln_ctx[0].split(':')[-1]
 
    else:
 
        retval = ln_ctx_global
 

	
 
    try:
 
        return int(retval)
 
    except:
 
        return
 

	
 
def _context_url(fileid=None):
 
    """
 
    Generates url for context lines
 
    
 
    :param fileid:
 
    """
 
    ig_ws = get_ignore_ws(fileid, request.GET)
 
    ln_ctx = (get_line_ctx(fileid, request.GET) or 3) * 2
 

	
 
    params = defaultdict(list)
 

	
 
    # global option
 
    if fileid is None:
 
        if ln_ctx > 0:
 
            params['context'] += [ln_ctx]
 

	
 
        if ig_ws:
 
            ig_ws_key = 'ignorews'
 
            ig_ws_val = 1
 

	
 
    # per file option
 
    else:
 
        params[fileid] += ['C:%s' % ln_ctx]
 
        ig_ws_key = fileid
 
        ig_ws_val = 'WS:%s' % 1
 
        
 
    if ig_ws:
 
        params[ig_ws_key] += [ig_ws_val]
 

	
 
    lbl = _('%s line context') % ln_ctx
 

	
 
    params['anchor'] = fileid
 
    return h.link_to(lbl, h.url.current(**params))
 

	
 
def wrap_to_table(str_):
 
    return '''<table class="code-difftable">
 
                <tr class="line">
 
                <td class="lineno new"></td>
 
                <td class="code"><pre>%s</pre></td>
 
                </tr>
 
              </table>''' % str_
 

	
 
class ChangesetController(BaseRepoController):
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def __before__(self):
 
        super(ChangesetController, self).__before__()
 
        c.affected_files_cut_off = 60
 

	
 
    def index(self, revision):
 
        ignore_whitespace = request.GET.get('ignorews') == '1'
 
        line_context = request.GET.get('context', 3)
 
        def wrap_to_table(str):
 

	
 
            return '''<table class="code-difftable">
 
                        <tr class="line">
 
                        <td class="lineno new"></td>
 
                        <td class="code"><pre>%s</pre></td>
 
                        </tr>
 
                      </table>''' % str
 
        c.anchor_url = anchor_url
 
        c.ignorews_url = _ignorews_url
 
        c.context_url = _context_url
 

	
 
        #get ranges of revisions if preset
 
        rev_range = revision.split('...')[:2]
 

	
 
        try:
 
            if len(rev_range) == 2:
 
                rev_start = rev_range[0]
 
                rev_end = rev_range[1]
 
                rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
 
                                                            end=rev_end)
 
            else:
 
                rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
 
@@ -121,28 +214,31 @@ class ChangesetController(BaseRepoContro
 
            #==================================================================
 
            for node in changeset.added:
 

	
 
                filenode_old = FileNode(node.path, '', EmptyChangeset())
 
                if filenode_old.is_binary or node.is_binary:
 
                    diff = wrap_to_table(_('binary file'))
 
                    st = (0, 0)
 
                else:
 
                    # in this case node.size is good parameter since those are
 
                    # added nodes and their size defines how many changes were
 
                    # made
 
                    c.sum_added += node.size
 
                    fid = h.FID(revision, node.path)
 
                    line_context_lcl = get_line_ctx(fid, request.GET)
 
                    ignore_whitespace_lcl = get_ignore_ws(fid, request.GET)
 
                    if c.sum_added < self.cut_off_limit:
 
                        f_gitdiff = diffs.get_gitdiff(filenode_old, node,
 
                                           ignore_whitespace=ignore_whitespace,
 
                                           context=line_context)
 
                                        ignore_whitespace=ignore_whitespace_lcl,
 
                                        context=line_context_lcl)
 
                        d = diffs.DiffProcessor(f_gitdiff, format='gitdiff')
 

	
 
                        st = d.stat()
 
                        diff = d.as_html()
 

	
 
                    else:
 
                        diff = wrap_to_table(_('Changeset is to big and '
 
                                               'was cut off, see raw '
 
                                               'changeset instead'))
 
                        c.cut_off = True
 
                        break
 

	
 
@@ -162,27 +258,30 @@ class ChangesetController(BaseRepoContro
 
                        filenode_old = changeset_parent.get_node(node.path)
 
                    except ChangesetError:
 
                        log.warning('Unable to fetch parent node for diff')
 
                        filenode_old = FileNode(node.path, '',
 
                                                EmptyChangeset())
 

	
 
                    if filenode_old.is_binary or node.is_binary:
 
                        diff = wrap_to_table(_('binary file'))
 
                        st = (0, 0)
 
                    else:
 

	
 
                        if c.sum_removed < self.cut_off_limit:
 
                            fid = h.FID(revision, node.path)
 
                            line_context_lcl = get_line_ctx(fid, request.GET)
 
                            ignore_whitespace_lcl = get_ignore_ws(fid, request.GET,)
 
                            f_gitdiff = diffs.get_gitdiff(filenode_old, node,
 
                                           ignore_whitespace=ignore_whitespace,
 
                                           context=line_context)
 
                                    ignore_whitespace=ignore_whitespace_lcl,
 
                                    context=line_context_lcl)
 
                            d = diffs.DiffProcessor(f_gitdiff,
 
                                                     format='gitdiff')
 
                            st = d.stat()
 
                            if (st[0] + st[1]) * 256 > self.cut_off_limit:
 
                                diff = wrap_to_table(_('Diff is to big '
 
                                                       'and was cut off, see '
 
                                                       'raw diff instead'))
 
                            else:
 
                                diff = d.as_html()
 

	
 
                            if diff:
 
                                c.sum_removed += len(diff)
rhodecode/lib/helpers.py
Show inline comments
 
@@ -43,24 +43,35 @@ from rhodecode.lib.markup_renderer impor
 
def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
 
    """
 
    Reset button
 
    """
 
    _set_input_attrs(attrs, type, name, value)
 
    _set_id_attr(attrs, id, name)
 
    convert_boolean_attrs(attrs, ["disabled"])
 
    return HTML.input(**attrs)
 

	
 
reset = _reset
 
safeid = _make_safe_id_component
 

	
 
        
 
def FID(raw_id,path):
 
    """
 
    Creates a uniqe ID for filenode based on it's path and revision
 
    
 
    :param raw_id:
 
    :param path:
 
    """
 
    return 'C-%s-%s' % (short_id(raw_id), safeid(safe_unicode(path)))
 

	
 

	
 
def get_token():
 
    """Return the current authentication token, creating one if one doesn't
 
    already exist.
 
    """
 
    token_key = "_authentication_token"
 
    from pylons import session
 
    if not token_key in session:
 
        try:
 
            token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
 
        except AttributeError: # Python < 2.4
 
            token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
 
        session[token_key] = token
rhodecode/public/css/style.css
Show inline comments
 
@@ -1881,25 +1881,27 @@ h3.files_location {
 
 
#changeset_content .container .right {
 
	float: right;
 
	width: 25%;
 
	text-align: right;
 
}
 
 
#changeset_content .container .left .message {
 
	font-style: italic;
 
	color: #556CB5;
 
	white-space: pre-wrap;
 
}
 
 
#changeset_content .container .left .message a:hover {
 
	text-decoration: none;
 
}
 
.cs_files .cur_cs {
 
	margin: 10px 2px;
 
	font-weight: bold;
 
}
 
 
.cs_files .node {
 
	float: left;
 
}
 
 
.cs_files .changes {
 
	float: right;
 
	color:#003367;
 
@@ -2245,45 +2247,45 @@ table.code-browser .browser-file {
 
    height: 16px;
 
}
 
.diffblock .changeset_file {
 
	background: url("../images/icons/file.png") no-repeat scroll 3px;
 
	text-align: left;
 
	float: left;
 
	padding: 2px 0px 2px 22px;
 
}
 
.diffblock .diff-menu-wrapper{
 
	float: left;
 
}
 
 
.diffblock .changeset_header .diff-menu{
 
.diffblock .diff-menu{
 
    position: absolute;
 
    background: none repeat scroll 0 0 #FFFFFF;
 
    border-color: #003367 #666666 #666666;
 
    border-right: 1px solid #666666;
 
    border-style: solid solid solid;
 
    border-width: 1px;
 
    box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
 
    margin-top:5px;
 
    margin-left:1px;
 
    
 
}
 
 
.diffblock .changeset_header .diff-menu ul li {
 
.diffblock  .diff-menu ul li {
 
	padding: 0px 0px 0px 0px !important;
 
}
 
.diffblock .changeset_header .diff-menu ul li a{
 
.diffblock  .diff-menu ul li a{
 
	display: block;
 
	padding: 3px 8px 3px 8px !important;
 
}
 
.diffblock .changeset_header .diff-menu ul li a:hover{
 
.diffblock  .diff-menu ul li a:hover{
 
    text-decoration: none;
 
    background-color: #EEEEEE;
 
}
 
table.code-browser .browser-dir {
 
	background: url("../images/icons/folder_16.png") no-repeat scroll 3px;
 
	height: 16px;
 
	padding-left: 20px;
 
	text-align: left;
 
}
 
 
.box .search {
 
	clear: both;
 
@@ -3680,24 +3682,29 @@ div.diffblock {
 
    line-height: 125%;
 
    -webkit-border-radius: 6px 6px 0px 0px;
 
    -moz-border-radius: 6px 6px 0px 0px;
 
    border-radius: 6px 6px 0px 0px;     
 
}
 
div.diffblock.margined{
 
    margin: 0px 20px 0px 20px;
 
}
 
div.diffblock .code-header{
 
    border-bottom: 1px solid #CCCCCC;
 
    background: #EEEEEE;
 
    padding:10px 0 10px 0;
 
    height: 14px;
 
}
 
div.diffblock .code-header .date{
 
    float:left;
 
    text-transform: uppercase;
 
}
 
div.diffblock .code-header div{
 
    margin-left:4px;
 
    font-weight: bold;
 
    font-size: 14px;
 
}
 
div.diffblock .code-body{
 
    background: #FFFFFF;
 
}
 
div.diffblock pre.raw{
 
    background: #FFFFFF;
 
    color:#000000;
rhodecode/templates/changeset/changeset.html
Show inline comments
 
@@ -9,52 +9,51 @@
 
<%def name="breadcrumbs_links()">
 
    ${h.link_to(u'Home',h.url('/'))}
 
    &raquo;
 
    ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
 
    &raquo;
 
    ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
 
</%def>
 

	
 
<%def name="page_nav()">
 
    ${self.menu('changelog')}     
 
</%def>
 

	
 
<%def name="fid(raw_id,path)" filter="strip">
 
  <% 
 
    return 'C-%s-%s' % (h.short_id(raw_id),h.safeid(h.safe_unicode(path))) 
 
  %>
 
</%def>
 

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
    </div>
 
    <div class="table">
 
		<div class="diffblock">
 
			<div class="code-header">
 
				<div>
 
                <span>${h.link_to(_('raw diff'),
 
				h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show'))}</span>
 
				 &raquo; <span>${h.link_to(_('download diff'),
 
				h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download'))}</span>
 
				<div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
 
                </div>
 
                <div class="date">${_('commit')} ${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} ${c.changeset.date}</div>
 
                <div class="diff-menu-wrapper">
 
                    <img class="diff-menu-activate" style="cursor: pointer" alt="diff-menu" src="${h.url('/images/icons/script_gear.png')}" />
 
                    <div class="diff-menu" style="display:none">
 
                        <ul>
 
                          <li>${h.link_to(_('raw diff'),h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show'))}</li>
 
                          <li>${h.link_to(_('download diff'),h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download'))}</li>
 
                          <li>${c.ignorews_url()}</li>
 
                          <li>${c.context_url()}</li>
 
                        </ul>
 
                    </div>                        
 
                </div>				
 
                <div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
 
			</div>
 
		</div>
 
	    <div id="changeset_content">
 
			<div class="container">
 
	             <div class="left">
 
	                 <div class="date">${_('commit')} ${c.changeset.revision}: ${h.short_id(c.changeset.raw_id)}@${c.changeset.date}</div>
 
	                 <div class="author">
 
	                     <div class="gravatar">
 
	                         <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/>
 
	                     </div>
 
	                     <span>${h.person(c.changeset.author)}</span><br/>
 
	                     <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
 
	                 </div>
 
	                 <div class="message">${h.link_to(h.wrap_paragraphs(c.changeset.message),h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</div>
 
	             </div>
 
	             <div class="right">
 
		             <div class="changes">
 
                        % if len(c.changeset.affected_files) <= c.affected_files_cut_off:	             
 
@@ -91,89 +90,91 @@
 
		             %endfor
 
		         </span>                                                                 
 
	                </div>              
 
	        </div>
 
	        <span style="font-size:1.1em;font-weight: bold">
 
	        ${_('%s files affected with %s additions and %s deletions.') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
 
	        </span>
 
	        <div class="cs_files">
 
	                %for change,filenode,diff,cs1,cs2,stat in c.changes:
 
	                    <div class="cs_${change}">
 
                            <div class="node">
 
                            %if change != 'removed':
 
                                ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=self.fid(filenode.changeset.raw_id,filenode.path)))}
 
                                ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path))}
 
                            %else:
 
                                ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=self.fid('',filenode.path)))}
 
                                ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
 
                            %endif
 
                            </div>
 
		                    <div class="changes">${h.fancy_file_stats(stat)}</div>
 
	                    </div>
 
	                %endfor
 
	                % if c.cut_off:
 
	                  ${_('Changeset was too big and was cut off...')}
 
	                % endif
 
	        </div>         
 
	    </div>
 
	    
 
    </div>
 
    	
 
	%for change,filenode,diff,cs1,cs2,stat in c.changes:
 
		%if change !='removed':
 
		<div style="clear:both;height:10px"></div>
 
		<div class="diffblock  margined comm" id="${self.fid(filenode.changeset.raw_id,filenode.path)}">
 
		<div id="${h.FID(filenode.changeset.raw_id,filenode.path)}" style="clear:both;height:90px;margin-top:-60px"></div>
 
		<div class="diffblock  margined comm">
 
			<div class="code-header">
 
				<div class="changeset_header">
 
					<div class="changeset_file">
 
						${h.link_to_if(change!='removed',h.safe_unicode(filenode.path),h.url('files_home',repo_name=c.repo_name,
 
						revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
 
					</div>
 
                    <div class="diff-menu-wrapper">
 
                        <img class="diff-menu-activate" style="margin-bottom:-6px;cursor: pointer" alt="diff-menu" src="${h.url('/images/icons/script_gear.png')}" />
 
                        <div class="diff-menu" style="display:none">
 
                            <ul>
 
                              <li>${h.link_to(_('diff'),h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff'))}</li>
 
                              <li>${h.link_to(_('raw diff'),h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw'))}</li>
 
                              <li>${h.link_to(_('download diff'),h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download'))}</li>
 
                              <li>${c.ignorews_url(h.FID(filenode.changeset.raw_id,filenode.path))}</li>
 
                              <li>${c.context_url(h.FID(filenode.changeset.raw_id,filenode.path))}</li>
 
                            </ul>
 
                        </div>                        
 
                    </div>
 
                    <span style="float:right;margin-top:-3px">
 
                      <label>
 
                      ${_('show inline comments')}
 
                      ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=self.fid(filenode.changeset.raw_id,filenode.path))}
 
                      ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(filenode.changeset.raw_id,filenode.path))}
 
                      </label>
 
                    </span>
 
				</div>
 
			</div>
 
			<div class="code-body">
 
                <div class="full_f_path" path="${filenode.path}"></div>        
 
				%if diff:
 
					${diff|n}
 
				%else:
 
					${_('No changes in this file')}
 
				%endif
 
			</div>
 
		</div>
 
		%endif
 
	%endfor
 
  
 
    <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
    ## template for inline comment form
 
    ${comment.comment_inline_form()}
 
    
 
    <div class="comments">
 
        <div class="comments-number">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
 
        
 
        %for path, lines in c.inline_comments:
 
            <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${self.fid(c.changeset.raw_id,path)}">
 
            <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.FID(c.changeset.raw_id,path)}">
 
            % for line,comments in lines.iteritems():
 
                <div class="inline-comment-placeholder-line" line="${line}" target_id="${h.safeid(h.safe_unicode(path))}"> 
 
                %for co in comments:
 
                    ${comment.comment_block(co)}
 
                %endfor
 
                </div>
 
            %endfor
 
            </div>
 
        %endfor        
 
        
 
        %for co in c.comments:
 
            ${comment.comment_block(co)}
0 comments (0 inline, 0 general)