source: peerreviewplugin/trunk/codereview/repo.py

Last change on this file was 18284, checked in by Cinc-th, 2 years ago

PeerReviewPlugin: allow to review added, copied and moved files in changesets. Up to now you had to visit the review pages for such files, see also [18271] which introduced an explanatory message for those files.

Refs #14007

File size: 9.9 KB
RevLine 
[15493]1# -*- coding: utf-8 -*-
2#
[18049]3# Copyright (C) 2016-2021 Cinc
[15493]4# All rights reserved.
5#
6# This software is licensed as described in the file COPYING.txt, which
7# you should have received as part of this distribution.
8#
9# Author: Cinc
10#
11import hashlib
12import os
[17277]13import posixpath
[18260]14from trac.versioncontrol.api import Node, NoSuchNode, RepositoryManager
[15493]15from .model import ReviewFileModel
[18276]16from .util import to_db_path
[15493]17
[18260]18
[15493]19__author__ = 'Cinc'
20__license__ = "BSD"
21
22
23def hash_from_file_node(node):
[18260]24    if node.kind != Node.FILE:
25        raise ValueError('Node is a directory. Must be a file.')
26
[15493]27    content = node.get_content()
28    blocksize = 4096
29    hasher = hashlib.sha256()
30
31    buf = content.read(blocksize)
32    while len(buf) > 0:
33        hasher.update(buf)
34        buf = content.read(blocksize)
35    return hasher.hexdigest()
36
37
38def repo_path_exists(env, path, reponame=''):
39    repos = RepositoryManager(env).get_repository(reponame)
40    if not repos:
41        return False
42
43    rev = repos.youngest_rev
44    try:
45        node = repos.get_node(path, rev)
46        return node.isdir
[17269]47    except NoSuchNode as e:
[15493]48        return False
49
50
51def get_node(repos, path, rev):
[18270]52    # TODO: from trac.versioncontrol.web_ui.util import get_existing_node is probably a better
53    #       way to do it...
54    rev = repos.normalize_rev(rev)
[15493]55    try:
56        return repos.get_node(path, rev)
[18270]57    except NoSuchNode:
[15493]58        return None
59
[18250]60from .svn_externals import parse_externals
[15493]61
[15594]62
[17407]63def get_nodes_for_dir(self, repodict, dir_node, fnodes, ignore_ext, incl_ext, excl_path, follow_ext, repo_name=''):
[15585]64    """Get file nodes recursively for a given directory node.
[15493]65
[15594]66    :param env: Trac environment object
67    :param repodict: dict holding info about known repositories
[15585]68    :param dir_node: a trac directory node
[18276]69    :param fnodes: list of file info nodes. Info for found files will be appended. Note that the path
70                   in the info dict is with leading '/'.
[15585]71    :param ignore_ext: list of file extensions to be ignored
[15594]72    :param follow_ext: if True follow externals to folders in the same or another repository
73
74    :return errors: list of errors. Empty list if no errors occurred.
[15585]75    """
[17277]76    if not self._externals_map:
77        for dummykey, value in self.external_map_section.options():
78            value = value.split()
79            if len(value) != 3:
80                self.log.warn("peerreview:repomap entry %s doesn't contain "
81                              "a space-separated list of three items, "
82                              "skipping.", dummykey)
83                continue
84            key, value, repo = value
85            self._externals_map[key] = [value, repo]
86
87    env = self.env
[15594]88    errors = []
[15493]89    for node in dir_node.get_entries():
90        if node.isdir:
[17407]91            errors += get_nodes_for_dir(self, repodict, node, fnodes, ignore_ext, incl_ext, excl_path, follow_ext,
92                                        repo_name)
[15594]93            if follow_ext:
94                props = node.get_properties()
95                try:
96                    for external in parse_externals(props['svn:externals']):
97                        try:
[17277]98                            # Create a valid paths from externals information. The path may point to virtual
99                            # repositories.
100                            base_url = external['url']
101                            while base_url:
102                                if base_url in self._externals_map or base_url == u'/':
103                                    break
104                                base_url, pref = posixpath.split(base_url)
105                            # base_url is the path head of the external
106                            ext_info = self._externals_map.get(base_url)
107                            file_path = repos = reponame = None
108                            if ext_info:
109                                file_path, reponame = ext_info
110                                file_path = file_path + external['url'][len(base_url):]
111                                repos = repodict[reponame]['repo']
[15594]112
[17277]113                            if file_path:
114                                rev = external['rev']
115                                if rev:
116                                    rev = repos.normalize_rev(rev)
117                                rev_or_latest = rev or repos.youngest_rev
118                                ext_node = get_node(repos, file_path, rev_or_latest)
[15594]119                            else:
[17277]120                                ext_node = None
121
122                            if ext_node and ext_node.isdir:
[17280]123                                errors += get_nodes_for_dir(self, repodict, ext_node, fnodes, ignore_ext, incl_ext,
[17407]124                                                            excl_path, follow_ext, reponame)
[17277]125                            else:
[15595]126                                txt = "No node for external path '%s' in repository '%s'. " \
127                                      "External: '%s %s' was ignored for directory '%s'." \
[17277]128                                      % (file_path, reponame, external['url'], external['dir'], node.name)
[15594]129                                env.log.warning(txt)
130                                errors.append(txt)
[17277]131                        except KeyError:  # Missing data in dictionary e.g. we try to use an unnamed repository
[15594]132                            txt = "External: '%s %s' was ignored for directory '%s'." %\
133                                  (external['url'], external['dir'], node.name)
134                            env.log.warning(txt)
135                            errors.append(txt)
136                except KeyError:  # property has no svn:externals
137                    pass
[15493]138        else:
[17281]139            for p in excl_path:
[18276]140                if to_db_path(node.path).startswith(p):
[17281]141                    break
[17280]142            else:
[17281]143                if incl_ext:
144                    if os.path.splitext(node.path)[1].lower() in incl_ext:
145                        fnodes.append({
[18276]146                            'path': to_db_path(node.path),
[17281]147                            'rev': node.rev,
148                            'change_rev':node.created_rev,
[17407]149                            'hash': hash_from_file_node(node),
150                            'reponame': repo_name
[17281]151                        })
152                else:
153                    if os.path.splitext(node.path)[1].lower() not in ignore_ext:
154                        fnodes.append({
[18276]155                            'path': to_db_path(node.path),
[17281]156                            'rev': node.rev,
157                            'change_rev':node.created_rev,
[17407]158                            'hash': hash_from_file_node(node),
159                            'reponame': repo_name
[17281]160                        })
[15594]161    return errors
[15493]162
163
[18284]164def file_lines_from_node(node, keyword_substitution=False):
165    """Get a list of lines for the text file represented by the given node.
[15518]166
[18284]167    Note that it is assumed that the text is utf-8.
168    End separators are stripped of.
169    """
170    if not node or node.isdir:
171        return []
[17462]172
[15702]173    dat = u''
[17257]174    if keyword_substitution:
175        content = node.get_processed_content()
176    else:
177        content = node.get_content()
[15518]178    res = content.read()
179    while res:
[15702]180        dat += res.decode('utf-8')  # We assume 'utf-8' here. In fact it may be anything.
[15518]181        res = content.read()
182    return dat.splitlines()
183
184
[15596]185def get_repository_dict(env):
186    """Get a dict with information about all repositories.
[15493]187
[15596]188    :param env: Trac environment object
189    :return: dict with key = reponame, value = dict with information about repository.
190
191    The information about a repository is queried using ''get_all_repositories'' from
192    RepositoryManager.
193    - For any real repository (that means not an alias) the Repository object
194      is inserted into the dictionary using the key 'repo'.
195    - For any real repository (that means not an alias) a prefix is calculated from the url info
196      and inserted using the key 'prefix'. This prefix is used to build paths into the repository.
197
[15585]198    """
[15596]199    repoman = RepositoryManager(env)
[15493]200
[15594]201    repolist = repoman.get_all_repositories()  # repolist is a dict with key = reponame, val = dict
202    for repo in repoman.get_real_repositories():
203        repolist[repo.reponame]['repo'] = repoman.get_repository(repo.reponame)
204        # We need the last part of the path later when following externals
205        try:
206            repolist[repo.reponame]['prefix'] = '/' + os.path.basename(repolist[repo.reponame]['url'].rstrip('/'))
207        except KeyError:
208            repolist[repo.reponame]['prefix'] = ''
[15596]209    return repolist
[15594]210
[15596]211
[17281]212def insert_project_files(self, src_path, project, ignore_ext, incl_ext, excl_path,
[17407]213                         follow_ext=False, rev=None, repo_name=''):
[15596]214    """Add project files to the database.
215
216    :param self: Trac component object
217    :param src_path
218    """
219    repolist = get_repository_dict(self.env)
220    try:
[17407]221        repos = repolist[repo_name]['repo']
[15596]222    except KeyError:
223        return
224
225    if not repos:
226        return
227
[15493]228    if rev:
229        rev = repos.normalize_rev(rev)
230    rev_or_latest = rev or repos.youngest_rev
231
232    root_node = get_node(repos, src_path, rev_or_latest)
233
[15594]234    fnodes = []
[15493]235    if root_node.isdir:
[17407]236        errors = get_nodes_for_dir(self, repolist, root_node, fnodes, ignore_ext, incl_ext, excl_path, follow_ext,
237                                   repo_name)
[15594]238    else:
239        errors = []
[15493]240
[15594]241    ReviewFileModel.delete_files_by_project_name(self.env, project)
[18049]242    with self.env.db_transaction as db:
[15493]243        cursor = db.cursor()
[15594]244        for item in fnodes:
[15493]245            cursor.execute("INSERT INTO peerreviewfile"
246                           "(review_id,path,line_start,line_end,repo,revision, changerevision,hash,project)"
[15585]247                           "VALUES (0, %s, 0, 0, %s, %s, %s, %s, %s)",
[17407]248                           (item['path'], item['reponame'], item['rev'], item['change_rev'], item['hash'], project))
[15594]249
250    return errors, len(fnodes)
Note: See TracBrowser for help on using the repository browser.