##// END OF EJS Templates
pr-versioning: implemented versioning for pull requests....
marcink -
r1368:9a887d01 default
parent child Browse files
Show More
@@ -66,113 +66,10 b' log = logging.getLogger(__name__)'
66 66
67 67
68 68 class PullrequestsController(BaseRepoController):
69
69 70 def __before__(self):
70 71 super(PullrequestsController, self).__before__()
71 72
72 def _load_compare_data(self, pull_request, inline_comments):
73 """
74 Load context data needed for generating compare diff
75
76 :param pull_request: object related to the request
77 :param enable_comments: flag to determine if comments are included
78 """
79 source_repo = pull_request.source_repo
80 source_ref_id = pull_request.source_ref_parts.commit_id
81
82 target_repo = pull_request.target_repo
83 target_ref_id = pull_request.target_ref_parts.commit_id
84
85 # despite opening commits for bookmarks/branches/tags, we always
86 # convert this to rev to prevent changes after bookmark or branch change
87 c.source_ref_type = 'rev'
88 c.source_ref = source_ref_id
89
90 c.target_ref_type = 'rev'
91 c.target_ref = target_ref_id
92
93 c.source_repo = source_repo
94 c.target_repo = target_repo
95
96 c.fulldiff = bool(request.GET.get('fulldiff'))
97
98 # diff_limit is the old behavior, will cut off the whole diff
99 # if the limit is applied otherwise will just hide the
100 # big files from the front-end
101 diff_limit = self.cut_off_limit_diff
102 file_limit = self.cut_off_limit_file
103
104 pre_load = ["author", "branch", "date", "message"]
105
106 c.commit_ranges = []
107 source_commit = EmptyCommit()
108 target_commit = EmptyCommit()
109 c.missing_requirements = False
110 try:
111 c.commit_ranges = [
112 source_repo.get_commit(commit_id=rev, pre_load=pre_load)
113 for rev in pull_request.revisions]
114
115 c.statuses = source_repo.statuses(
116 [x.raw_id for x in c.commit_ranges])
117
118 target_commit = source_repo.get_commit(
119 commit_id=safe_str(target_ref_id))
120 source_commit = source_repo.get_commit(
121 commit_id=safe_str(source_ref_id))
122 except RepositoryRequirementError:
123 c.missing_requirements = True
124
125 # auto collapse if we have more than limit
126 collapse_limit = diffs.DiffProcessor._collapse_commits_over
127 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
128
129 c.changes = {}
130 c.missing_commits = False
131 if (c.missing_requirements or
132 isinstance(source_commit, EmptyCommit) or
133 source_commit == target_commit):
134 _parsed = []
135 c.missing_commits = True
136 else:
137 vcs_diff = PullRequestModel().get_diff(pull_request)
138 diff_processor = diffs.DiffProcessor(
139 vcs_diff, format='newdiff', diff_limit=diff_limit,
140 file_limit=file_limit, show_full_diff=c.fulldiff)
141
142 _parsed = diff_processor.prepare()
143 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
144
145 included_files = {}
146 for f in _parsed:
147 included_files[f['filename']] = f['stats']
148
149 c.deleted_files = [fname for fname in inline_comments if
150 fname not in included_files]
151
152 c.deleted_files_comments = collections.defaultdict(dict)
153 for fname, per_line_comments in inline_comments.items():
154 if fname in c.deleted_files:
155 c.deleted_files_comments[fname]['stats'] = 0
156 c.deleted_files_comments[fname]['comments'] = list()
157 for lno, comments in per_line_comments.items():
158 c.deleted_files_comments[fname]['comments'].extend(comments)
159
160 def _node_getter(commit):
161 def get_node(fname):
162 try:
163 return commit.get_node(fname)
164 except NodeDoesNotExistError:
165 return None
166 return get_node
167
168 c.diffset = codeblocks.DiffSet(
169 repo_name=c.repo_name,
170 source_repo_name=c.source_repo.repo_name,
171 source_node_getter=_node_getter(target_commit),
172 target_node_getter=_node_getter(source_commit),
173 comments=inline_comments
174 ).render_patchset(_parsed, target_commit.raw_id, source_commit.raw_id)
175
176 73 def _extract_ordering(self, request):
177 74 column_index = safe_int(request.GET.get('order[0][column]'))
178 75 order_dir = request.GET.get('order[0][dir]', 'desc')
@@ -693,71 +590,53 b' class PullrequestsController(BaseRepoCon'
693 590
694 591 pull_request_display_obj = PullRequest.get_pr_display_object(
695 592 pull_request_obj, _org_pull_request_obj)
593
696 594 return _org_pull_request_obj, pull_request_obj, \
697 595 pull_request_display_obj, at_version
698 596
699 def _get_pr_version_changes(self, version, pull_request_latest):
700 """
701 Generate changes commits, and diff data based on the current pr version
702 """
703
704 #TODO(marcink): save those changes as JSON metadata for chaching later.
705
706 # fake the version to add the "initial" state object
707 pull_request_initial = PullRequest.get_pr_display_object(
708 pull_request_latest, pull_request_latest,
709 internal_methods=['get_commit', 'versions'])
710 pull_request_initial.revisions = []
711 pull_request_initial.source_repo.get_commit = types.MethodType(
712 lambda *a, **k: EmptyCommit(), pull_request_initial)
713 pull_request_initial.source_repo.scm_instance = types.MethodType(
714 lambda *a, **k: EmptyRepository(), pull_request_initial)
715
716 _changes_versions = [pull_request_latest] + \
717 list(reversed(c.versions)) + \
718 [pull_request_initial]
719
720 if version == 'latest':
721 index = 0
722 else:
723 for pos, prver in enumerate(_changes_versions):
724 ver = getattr(prver, 'pull_request_version_id', -1)
725 if ver == safe_int(version):
726 index = pos
727 break
728 else:
729 index = 0
730
731 cur_obj = _changes_versions[index]
732 prev_obj = _changes_versions[index + 1]
733
734 old_commit_ids = set(prev_obj.revisions)
735 new_commit_ids = set(cur_obj.revisions)
736
737 changes = PullRequestModel()._calculate_commit_id_changes(
738 old_commit_ids, new_commit_ids)
739
740 old_diff_data, new_diff_data = PullRequestModel()._generate_update_diffs(
741 cur_obj, prev_obj)
742 file_changes = PullRequestModel()._calculate_file_changes(
743 old_diff_data, new_diff_data)
744 return changes, file_changes
745
746 597 @LoginRequired()
747 598 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
748 599 'repository.admin')
749 600 def show(self, repo_name, pull_request_id):
750 601 pull_request_id = safe_int(pull_request_id)
751 602 version = request.GET.get('version')
603 from_version = request.GET.get('from_version') or version
752 604 merge_checks = request.GET.get('merge_checks')
605 c.fulldiff = str2bool(request.GET.get('fulldiff'))
606
607 # register for JS templates
608 c.template_context['pull_request_data']['pull_request_id'] = \
609 pull_request_id
753 610
754 611 (pull_request_latest,
755 612 pull_request_at_ver,
756 613 pull_request_display_obj,
757 at_version) = self._get_pr_version(pull_request_id, version=version)
614 at_version) = self._get_pr_version(
615 pull_request_id, version=version)
616 versions = pull_request_display_obj.versions()
617
618 c.at_version = at_version
619 c.at_version_num = (at_version
620 if at_version and at_version != 'latest'
621 else None)
622 c.at_version_pos = ChangesetComment.get_index_from_version(
623 c.at_version_num, versions)
758 624
759 c.template_context['pull_request_data']['pull_request_id'] = \
760 pull_request_id
625 (prev_pull_request_latest,
626 prev_pull_request_at_ver,
627 prev_pull_request_display_obj,
628 prev_at_version) = self._get_pr_version(
629 pull_request_id, version=from_version)
630
631 c.from_version = prev_at_version
632 c.from_version_num = (prev_at_version
633 if prev_at_version and prev_at_version != 'latest'
634 else None)
635 c.from_version_pos = ChangesetComment.get_index_from_version(
636 c.from_version_num, versions)
637
638 # define if we're in COMPARE mode or VIEW at version mode
639 compare = at_version != prev_at_version
761 640
762 641 # pull_requests repo_name we opened it against
763 642 # ie. target_repo must match
@@ -767,12 +646,12 b' class PullrequestsController(BaseRepoCon'
767 646 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(
768 647 pull_request_at_ver)
769 648
770 c.ancestor = None # TODO: add ancestor here
649 c.ancestor = None # empty ancestor hidden in display
771 650 c.pull_request = pull_request_display_obj
772 651 c.pull_request_latest = pull_request_latest
773 652
774 653 pr_closed = pull_request_latest.is_closed()
775 if at_version and not at_version == 'latest':
654 if compare or (at_version and not at_version == 'latest'):
776 655 c.allowed_to_change_status = False
777 656 c.allowed_to_update = False
778 657 c.allowed_to_merge = False
@@ -789,35 +668,50 b' class PullrequestsController(BaseRepoCon'
789 668 pull_request_latest, c.rhodecode_user) and not pr_closed
790 669 c.allowed_to_comment = not pr_closed
791 670
792 cc_model = CommentsModel()
671 # check merge capabilities
672 _merge_check = MergeCheck.validate(
673 pull_request_latest, user=c.rhodecode_user)
674 c.pr_merge_errors = _merge_check.error_details
675 c.pr_merge_possible = not _merge_check.failed
676 c.pr_merge_message = _merge_check.merge_msg
793 677
678 if merge_checks:
679 return render('/pullrequests/pullrequest_merge_checks.mako')
680
681 comments_model = CommentsModel()
682
683 # reviewers and statuses
794 684 c.pull_request_reviewers = pull_request_at_ver.reviewers_statuses()
685 allowed_reviewers = [x[0].user_id for x in c.pull_request_reviewers]
795 686 c.pull_request_review_status = pull_request_at_ver.calculated_review_status()
796 687
797 c.versions = pull_request_display_obj.versions()
798 c.at_version = at_version
799 c.at_version_num = at_version if at_version and at_version != 'latest' else None
800 c.at_version_pos = ChangesetComment.get_index_from_version(
801 c.at_version_num, c.versions)
802
803 688 # GENERAL COMMENTS with versions #
804 q = cc_model._all_general_comments_of_pull_request(pull_request_latest)
689 q = comments_model._all_general_comments_of_pull_request(pull_request_latest)
690 q = q.order_by(ChangesetComment.comment_id.asc())
805 691 general_comments = q.order_by(ChangesetComment.pull_request_version_id.asc())
806 692
807 693 # pick comments we want to render at current version
808 c.comment_versions = cc_model.aggregate_comments(
809 general_comments, c.versions, c.at_version_num)
694 c.comment_versions = comments_model.aggregate_comments(
695 general_comments, versions, c.at_version_num)
810 696 c.comments = c.comment_versions[c.at_version_num]['until']
811 697
812 698 # INLINE COMMENTS with versions #
813 q = cc_model._all_inline_comments_of_pull_request(pull_request_latest)
699 q = comments_model._all_inline_comments_of_pull_request(pull_request_latest)
700 q = q.order_by(ChangesetComment.comment_id.asc())
814 701 inline_comments = q.order_by(ChangesetComment.pull_request_version_id.asc())
815 c.inline_versions = cc_model.aggregate_comments(
816 inline_comments, c.versions, c.at_version_num, inline=True)
702 c.inline_versions = comments_model.aggregate_comments(
703 inline_comments, versions, c.at_version_num, inline=True)
704
705 # inject latest version
706 latest_ver = PullRequest.get_pr_display_object(
707 pull_request_latest, pull_request_latest)
708
709 c.versions = versions + [latest_ver]
817 710
818 711 # if we use version, then do not show later comments
819 712 # than current version
820 display_inline_comments = collections.defaultdict(lambda: collections.defaultdict(list))
713 display_inline_comments = collections.defaultdict(
714 lambda: collections.defaultdict(list))
821 715 for co in inline_comments:
822 716 if c.at_version_num:
823 717 # pick comments that are at least UPTO given version, so we
@@ -831,17 +725,126 b' class PullrequestsController(BaseRepoCon'
831 725 if should_render:
832 726 display_inline_comments[co.f_path][co.line_no].append(co)
833 727
834 _merge_check = MergeCheck.validate(
835 pull_request_latest, user=c.rhodecode_user)
836 c.pr_merge_errors = _merge_check.error_details
837 c.pr_merge_possible = not _merge_check.failed
838 c.pr_merge_message = _merge_check.merge_msg
728 # load diff data into template context, if we use compare mode then
729 # diff is calculated based on changes between versions of PR
730
731 source_repo = pull_request_at_ver.source_repo
732 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
733
734 target_repo = pull_request_at_ver.target_repo
735 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
736
737 if compare:
738 # in compare switch the diff base to latest commit from prev version
739 target_ref_id = prev_pull_request_display_obj.revisions[0]
740
741 # despite opening commits for bookmarks/branches/tags, we always
742 # convert this to rev to prevent changes after bookmark or branch change
743 c.source_ref_type = 'rev'
744 c.source_ref = source_ref_id
745
746 c.target_ref_type = 'rev'
747 c.target_ref = target_ref_id
748
749 c.source_repo = source_repo
750 c.target_repo = target_repo
751
752 # diff_limit is the old behavior, will cut off the whole diff
753 # if the limit is applied otherwise will just hide the
754 # big files from the front-end
755 diff_limit = self.cut_off_limit_diff
756 file_limit = self.cut_off_limit_file
757
758 c.commit_ranges = []
759 source_commit = EmptyCommit()
760 target_commit = EmptyCommit()
761 c.missing_requirements = False
762
763 # try first shadow repo, fallback to regular repo
764 try:
765 commits_source_repo = pull_request_latest.get_shadow_repo()
766 except Exception:
767 log.debug('Failed to get shadow repo', exc_info=True)
768 commits_source_repo = source_repo.scm_instance()
769
770 c.commits_source_repo = commits_source_repo
771 commit_cache = {}
772 try:
773 pre_load = ["author", "branch", "date", "message"]
774 show_revs = pull_request_at_ver.revisions
775 for rev in show_revs:
776 comm = commits_source_repo.get_commit(
777 commit_id=rev, pre_load=pre_load)
778 c.commit_ranges.append(comm)
779 commit_cache[comm.raw_id] = comm
839 780
840 if merge_checks:
841 return render('/pullrequests/pullrequest_merge_checks.mako')
781 target_commit = commits_source_repo.get_commit(
782 commit_id=safe_str(target_ref_id))
783 source_commit = commits_source_repo.get_commit(
784 commit_id=safe_str(source_ref_id))
785 except CommitDoesNotExistError:
786 pass
787 except RepositoryRequirementError:
788 log.warning(
789 'Failed to get all required data from repo', exc_info=True)
790 c.missing_requirements = True
791
792 c.statuses = source_repo.statuses(
793 [x.raw_id for x in c.commit_ranges])
794
795 # auto collapse if we have more than limit
796 collapse_limit = diffs.DiffProcessor._collapse_commits_over
797 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
798 c.compare_mode = compare
799
800 c.missing_commits = False
801 if (c.missing_requirements or isinstance(source_commit, EmptyCommit)
802 or source_commit == target_commit):
803
804 c.missing_commits = True
805 else:
806 vcs_diff = PullRequestModel().get_diff(
807 commits_source_repo, source_ref_id, target_ref_id)
808
809 diff_processor = diffs.DiffProcessor(
810 vcs_diff, format='newdiff', diff_limit=diff_limit,
811 file_limit=file_limit, show_full_diff=c.fulldiff)
842 812
843 # load compare data into template context
844 self._load_compare_data(pull_request_at_ver, display_inline_comments)
813 _parsed = diff_processor.prepare()
814 c.limited_diff = isinstance(_parsed, diffs.LimitedDiffContainer)
815
816 def _node_getter(commit):
817 def get_node(fname):
818 try:
819 return commit.get_node(fname)
820 except NodeDoesNotExistError:
821 return None
822
823 return get_node
824
825 diffset = codeblocks.DiffSet(
826 repo_name=c.repo_name,
827 source_repo_name=c.source_repo.repo_name,
828 source_node_getter=_node_getter(target_commit),
829 target_node_getter=_node_getter(source_commit),
830 comments=display_inline_comments
831 )
832 c.diffset = diffset.render_patchset(
833 _parsed, target_commit.raw_id, source_commit.raw_id)
834
835 # calculate removed files that are bound to comments
836 comment_deleted_files = [
837 fname for fname in display_inline_comments
838 if fname not in c.diffset.file_stats]
839
840 c.deleted_files_comments = collections.defaultdict(dict)
841 for fname, per_line_comments in display_inline_comments.items():
842 if fname in comment_deleted_files:
843 c.deleted_files_comments[fname]['stats'] = 0
844 c.deleted_files_comments[fname]['comments'] = list()
845 for lno, comments in per_line_comments.items():
846 c.deleted_files_comments[fname]['comments'].extend(
847 comments)
845 848
846 849 # this is a hack to properly display links, when creating PR, the
847 850 # compare view and others uses different notation, and
@@ -856,21 +859,55 b' class PullrequestsController(BaseRepoCon'
856 859 statuses = ChangesetStatus.STATUSES
857 860 c.commit_statuses = statuses
858 861
859 c.changes = None
860 c.file_changes = None
862 c.show_version_changes = not pr_closed
863 if c.show_version_changes:
864 cur_obj = pull_request_at_ver
865 prev_obj = prev_pull_request_at_ver
866
867 old_commit_ids = prev_obj.revisions
868 new_commit_ids = cur_obj.revisions
869 commit_changes = PullRequestModel()._calculate_commit_id_changes(
870 old_commit_ids, new_commit_ids)
871 c.commit_changes_summary = commit_changes
872
873 # calculate the diff for commits between versions
874 c.commit_changes = []
875 mark = lambda cs, fw: list(
876 h.itertools.izip_longest([], cs, fillvalue=fw))
877 for c_type, raw_id in mark(commit_changes.added, 'a') \
878 + mark(commit_changes.removed, 'r') \
879 + mark(commit_changes.common, 'c'):
861 880
862 c.show_version_changes = 1 # control flag, not used yet
881 if raw_id in commit_cache:
882 commit = commit_cache[raw_id]
883 else:
884 try:
885 commit = commits_source_repo.get_commit(raw_id)
886 except CommitDoesNotExistError:
887 # in case we fail extracting still use "dummy" commit
888 # for display in commit diff
889 commit = h.AttributeDict(
890 {'raw_id': raw_id,
891 'message': 'EMPTY or MISSING COMMIT'})
892 c.commit_changes.append([c_type, commit])
863 893
864 if at_version and c.show_version_changes:
865 c.changes, c.file_changes = self._get_pr_version_changes(
866 version, pull_request_latest)
894 # current user review statuses for each version
895 c.review_versions = {}
896 if c.rhodecode_user.user_id in allowed_reviewers:
897 for co in general_comments:
898 if co.author.user_id == c.rhodecode_user.user_id:
899 # each comment has a status change
900 status = co.status_change
901 if status:
902 _ver_pr = status[0].comment.pull_request_version_id
903 c.review_versions[_ver_pr] = status[0]
867 904
868 905 return render('/pullrequests/pullrequest_show.mako')
869 906
870 907 @LoginRequired()
871 908 @NotAnonymous()
872 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
873 'repository.admin')
909 @HasRepoPermissionAnyDecorator(
910 'repository.read', 'repository.write', 'repository.admin')
874 911 @auth.CSRFRequired()
875 912 @jsonify
876 913 def comment(self, repo_name, pull_request_id):
@@ -382,6 +382,7 b' class DiffSet(object):'
382 382 lines_deleted=0,
383 383 changed_files=0,
384 384 files=[],
385 file_stats={},
385 386 limited_diff=isinstance(patchset, LimitedDiffContainer),
386 387 repo_name=self.repo_name,
387 388 source_repo_name=self.source_repo_name,
@@ -389,6 +390,7 b' class DiffSet(object):'
389 390 target_ref=target_ref,
390 391 ))
391 392 for patch in patchset:
393 diffset.file_stats[patch['filename']] = patch['stats']
392 394 filediff = self.render_patch(patch)
393 395 filediff.diffset = diffset
394 396 diffset.files.append(filediff)
@@ -37,6 +37,7 b' import time'
37 37 import string
38 38 import hashlib
39 39 import pygments
40 import itertools
40 41
41 42 from datetime import datetime
42 43 from functools import partial
@@ -3028,7 +3028,7 b' class ChangesetStatus(Base, BaseModel):'
3028 3028 pull_request = relationship('PullRequest', lazy='joined')
3029 3029
3030 3030 def __unicode__(self):
3031 return u"<%s('%s[%s]:%s')>" % (
3031 return u"<%s('%s[v%s]:%s')>" % (
3032 3032 self.__class__.__name__,
3033 3033 self.status, self.version, self.author
3034 3034 )
@@ -3254,7 +3254,6 b' class PullRequest(Base, _PullRequestBase'
3254 3254 cascade="all, delete, delete-orphan",
3255 3255 lazy='dynamic')
3256 3256
3257
3258 3257 @classmethod
3259 3258 def get_pr_display_object(cls, pull_request_obj, org_pull_request_obj,
3260 3259 internal_methods=None):
@@ -3290,6 +3289,10 b' class PullRequest(Base, _PullRequestBase'
3290 3289 def is_closed(self):
3291 3290 return pull_request_obj.is_closed()
3292 3291
3292 @property
3293 def pull_request_version_id(self):
3294 return getattr(pull_request_obj, 'pull_request_version_id', None)
3295
3293 3296 attrs = StrictAttributeDict(pull_request_obj.get_api_data())
3294 3297
3295 3298 attrs.author = StrictAttributeDict(
@@ -235,7 +235,7 b' class NotificationModel(BaseModel):'
235 235 .filter(UserNotification.notification == notification)\
236 236 .filter(UserNotification.user == user).scalar()
237 237
238 def make_description(self, notification, show_age=True):
238 def make_description(self, notification, show_age=True, translate=None):
239 239 """
240 240 Creates a human readable description based on properties
241 241 of notification object
@@ -273,6 +273,8 b' class NotificationModel(BaseModel):'
273 273 if show_age:
274 274 template = templates[0]
275 275 date_or_age = h.age(notification.created_on)
276 if translate:
277 date_or_age = translate(date_or_age)
276 278 else:
277 279 template = templates[1]
278 280 date_or_age = h.format_date(notification.created_on)
@@ -50,7 +50,7 b' from rhodecode.model.changeset_status im'
50 50 from rhodecode.model.comment import CommentsModel
51 51 from rhodecode.model.db import (
52 52 PullRequest, PullRequestReviewers, ChangesetStatus,
53 PullRequestVersion, ChangesetComment)
53 PullRequestVersion, ChangesetComment, Repository)
54 54 from rhodecode.model.meta import Session
55 55 from rhodecode.model.notification import NotificationModel, \
56 56 EmailNotificationModel
@@ -728,13 +728,23 b' class PullRequestModel(BaseModel):'
728 728 return version
729 729
730 730 def _generate_update_diffs(self, pull_request, pull_request_version):
731
731 732 diff_context = (
732 733 self.DIFF_CONTEXT +
733 734 CommentsModel.needed_extra_diff_context())
735
736 source_repo = pull_request_version.source_repo
737 source_ref_id = pull_request_version.source_ref_parts.commit_id
738 target_ref_id = pull_request_version.target_ref_parts.commit_id
734 739 old_diff = self._get_diff_from_pr_or_version(
735 pull_request_version, context=diff_context)
740 source_repo, source_ref_id, target_ref_id, context=diff_context)
741
742 source_repo = pull_request.source_repo
743 source_ref_id = pull_request.source_ref_parts.commit_id
744 target_ref_id = pull_request.target_ref_parts.commit_id
745
736 746 new_diff = self._get_diff_from_pr_or_version(
737 pull_request, context=diff_context)
747 source_repo, source_ref_id, target_ref_id, context=diff_context)
738 748
739 749 old_diff_data = diffs.DiffProcessor(old_diff)
740 750 old_diff_data.prepare()
@@ -768,10 +778,11 b' class PullRequestModel(BaseModel):'
768 778 Session().add(comment)
769 779
770 780 def _calculate_commit_id_changes(self, old_ids, new_ids):
771 added = new_ids.difference(old_ids)
772 common = old_ids.intersection(new_ids)
773 removed = old_ids.difference(new_ids)
774 return ChangeTuple(added, common, removed)
781 added = [x for x in new_ids if x not in old_ids]
782 common = [x for x in new_ids if x in old_ids]
783 removed = [x for x in old_ids if x not in new_ids]
784 total = new_ids
785 return ChangeTuple(added, common, removed, total)
775 786
776 787 def _calculate_file_changes(self, old_diff_data, new_diff_data):
777 788
@@ -1261,20 +1272,20 b' class PullRequestModel(BaseModel):'
1261 1272 raise EmptyRepositoryError()
1262 1273 return groups, selected
1263 1274
1264 def get_diff(self, pull_request, context=DIFF_CONTEXT):
1265 pull_request = self.__get_pull_request(pull_request)
1266 return self._get_diff_from_pr_or_version(pull_request, context=context)
1275 def get_diff(self, source_repo, source_ref_id, target_ref_id, context=DIFF_CONTEXT):
1276 return self._get_diff_from_pr_or_version(
1277 source_repo, source_ref_id, target_ref_id, context=context)
1267 1278
1268 def _get_diff_from_pr_or_version(self, pr_or_version, context):
1269 source_repo = pr_or_version.source_repo
1270
1271 # we swap org/other ref since we run a simple diff on one repo
1272 target_ref_id = pr_or_version.target_ref_parts.commit_id
1273 source_ref_id = pr_or_version.source_ref_parts.commit_id
1279 def _get_diff_from_pr_or_version(
1280 self, source_repo, source_ref_id, target_ref_id, context):
1274 1281 target_commit = source_repo.get_commit(
1275 1282 commit_id=safe_str(target_ref_id))
1276 source_commit = source_repo.get_commit(commit_id=safe_str(source_ref_id))
1283 source_commit = source_repo.get_commit(
1284 commit_id=safe_str(source_ref_id))
1285 if isinstance(source_repo, Repository):
1277 1286 vcs_repo = source_repo.scm_instance()
1287 else:
1288 vcs_repo = source_repo
1278 1289
1279 1290 # TODO: johbo: In the context of an update, we cannot reach
1280 1291 # the old commit anymore with our normal mechanisms. It needs
@@ -1403,7 +1414,7 b' class MergeCheck(object):'
1403 1414
1404 1415
1405 1416 ChangeTuple = namedtuple('ChangeTuple',
1406 ['added', 'common', 'removed'])
1417 ['added', 'common', 'removed', 'total'])
1407 1418
1408 1419 FileChangeTuple = namedtuple('FileChangeTuple',
1409 1420 ['added', 'modified', 'removed'])
@@ -1393,6 +1393,14 b' table.integrations {'
1393 1393 }
1394 1394 }
1395 1395
1396 .subtitle-compare {
1397 margin: -15px 0px 0px 0px;
1398 }
1399
1400 .comments-summary-td {
1401 border-top: 1px dashed @grey5;
1402 }
1403
1396 1404 // new entry in group_members
1397 1405 .td-author-new-entry {
1398 1406 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
@@ -1422,6 +1430,57 b' table.integrations {'
1422 1430 margin-left: 5px;
1423 1431 }
1424 1432
1433 .compare_view_commits {
1434 .color-a {
1435 color: @alert1;
1436 }
1437
1438 .color-c {
1439 color: @color3;
1440 }
1441
1442 .color-r {
1443 color: @color5;
1444 }
1445
1446 .color-a-bg {
1447 background-color: @alert1;
1448 }
1449
1450 .color-c-bg {
1451 background-color: @alert3;
1452 }
1453
1454 .color-r-bg {
1455 background-color: @alert2;
1456 }
1457
1458 .color-a-border {
1459 border: 1px solid @alert1;
1460 }
1461
1462 .color-c-border {
1463 border: 1px solid @alert3;
1464 }
1465
1466 .color-r-border {
1467 border: 1px solid @alert2;
1468 }
1469
1470 .commit-change-indicator {
1471 width: 15px;
1472 height: 15px;
1473 position: relative;
1474 left: 15px;
1475 }
1476
1477 .commit-change-content {
1478 text-align: center;
1479 vertical-align: middle;
1480 line-height: 15px;
1481 }
1482 }
1483
1425 1484 .compare_view_files {
1426 1485 width: 100%;
1427 1486
@@ -1663,9 +1722,26 b' BIN_FILENODE = 7'
1663 1722 }
1664 1723
1665 1724 .pr-versions {
1725 font-size: 1.1em;
1726
1727 table {
1728 padding: 0px 5px;
1729 }
1730
1731 td {
1732 line-height: 15px;
1733 }
1734
1735 .flag_status {
1736 margin: 0;
1737 }
1738
1739 .compare-radio-button {
1666 1740 position: relative;
1667 top: 6px;
1668 }
1741 top: -3px;
1742 }
1743 }
1744
1669 1745
1670 1746 #close_pull_request {
1671 1747 margin-right: 0px;
@@ -315,13 +315,35 b''
315 315 var cid = target_expand.data('commitId');
316 316
317 317 if (target_expand.hasClass('open')){
318 $('#c-'+cid).css({'height': '1.5em', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
319 $('#t-'+cid).css({'height': 'auto', 'line-height': '.9em', 'text-overflow': 'ellipsis', 'overflow':'hidden', 'white-space':'nowrap'});
318 $('#c-' + cid).css({
319 'height': '1.5em',
320 'white-space': 'nowrap',
321 'text-overflow': 'ellipsis',
322 'overflow': 'hidden'
323 });
324 $('#t-' + cid).css({
325 'height': 'auto',
326 'line-height': '.9em',
327 'text-overflow': 'ellipsis',
328 'overflow': 'hidden',
329 'white-space': 'nowrap'
330 });
320 331 target_expand.removeClass('open');
321 332 }
322 333 else {
323 $('#c-'+cid).css({'height': 'auto', 'white-space': 'pre-line', 'text-overflow': 'initial', 'overflow':'visible'});
324 $('#t-'+cid).css({'height': 'auto', 'max-height': 'none', 'text-overflow': 'initial', 'overflow':'visible', 'white-space':'normal'});
334 $('#c-' + cid).css({
335 'height': 'auto',
336 'white-space': 'pre-line',
337 'text-overflow': 'initial',
338 'overflow': 'visible'
339 });
340 $('#t-' + cid).css({
341 'height': 'auto',
342 'max-height': 'none',
343 'text-overflow': 'initial',
344 'overflow': 'visible',
345 'white-space': 'normal'
346 });
325 347 target_expand.addClass('open');
326 348 }
327 349 // redraw the graph
@@ -134,13 +134,6 b' collapse_all = len(diffset.files) > coll'
134 134 '%(num)s files changed: %(linesadd)s inserted, %(linesdel)s deleted', diffset.changed_files) % {'num': diffset.changed_files, 'linesadd': diffset.lines_added, 'linesdel': diffset.lines_deleted}}
135 135 %endif
136 136
137 <% at_ver = getattr(c, 'at_version_pos', None) %>
138 % if at_ver:
139 <div class="pull-right">
140 ${_('Showing changes at version %d') % at_ver}
141 </div>
142 % endif
143
144 137 </h2>
145 138 </div>
146 139
@@ -1,4 +1,5 b''
1 1 <%inherit file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
2 3
3 4 <%def name="title()">
4 5 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
@@ -166,46 +167,22 b''
166 167
167 168 <div class="field">
168 169 <div class="label-summary">
169 <label>${_('Versions')} (${len(c.versions)+1}):</label>
170 <label>${_('Versions')}:</label>
170 171 </div>
171 172
173 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
174 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
175
172 176 <div class="pr-versions">
173 177 % if c.show_version_changes:
178 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
179 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
180 <div id="show-pr-versions" class="input btn btn-link" onclick="return versionController.toggleVersionView(this)"
181 data-toggle-on="${ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
182 data-toggle-off="${_('Hide all versions of this pull request')}">
183 ${ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
184 </div>
174 185 <table>
175 ## CURRENTLY SELECT PR VERSION
176 <tr class="version-pr" style="display: ${'' if c.at_version_num is None else 'none'}">
177 <td>
178 % if c.at_version_num is None:
179 <i class="icon-ok link"></i>
180 % else:
181 <i class="icon-comment"></i>
182 <code>
183 ${len(c.comment_versions[None]['at'])}/${len(c.inline_versions[None]['at'])}
184 </code>
185 % endif
186 </td>
187 <td>
188 <code>
189 % if c.versions:
190 <a href="${h.url.current(version='latest')}">${_('latest')}</a>
191 % else:
192 ${_('initial')}
193 % endif
194 </code>
195 </td>
196 <td>
197 <code>${c.pull_request_latest.source_ref_parts.commit_id[:6]}</code>
198 </td>
199 <td>
200 ${_('created')} ${h.age_component(c.pull_request_latest.updated_on)}
201 </td>
202 <td align="right">
203 % if c.versions and c.at_version_num in [None, 'latest']:
204 <span id="show-pr-versions" class="btn btn-link" onclick="$('.version-pr').show(); $(this).hide(); return false">${_('Show all versions')}</span>
205 % endif
206 </td>
207 </tr>
208
209 186 ## SHOW ALL VERSIONS OF PR
210 187 <% ver_pr = None %>
211 188
@@ -213,46 +190,52 b''
213 190 <% ver_pos = data[0] %>
214 191 <% ver = data[1] %>
215 192 <% ver_pr = ver.pull_request_version_id %>
193 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
216 194
217 <tr class="version-pr" style="display: ${'' if c.at_version_num == ver_pr else 'none'}">
195 <tr class="version-pr" style="display: ${display_row}">
196 <td>
197 <code>
198 <a href="${h.url.current(version=ver_pr or 'latest')}">v${ver_pos}</a>
199 </code>
200 </td>
218 201 <td>
219 % if c.at_version_num == ver_pr:
220 <i class="icon-ok link"></i>
221 % else:
202 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
203 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
204 </td>
205 <td>
206 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
207 <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}">
208 </div>
209 </td>
210 <td>
211 % if c.at_version_num != ver_pr:
222 212 <i class="icon-comment"></i>
223 <code class="tooltip" title="${_('Comment from pull request version {0}, general:{1} inline{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
213 <code class="tooltip" title="${_('Comment from pull request version {0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
224 214 ${len(c.comment_versions[ver_pr]['at'])}/${len(c.inline_versions[ver_pr]['at'])}
225 215 </code>
226 216 % endif
227 217 </td>
228 218 <td>
229 <code>
230 <a href="${h.url.current(version=ver_pr)}">v${ver_pos}</a>
231 </code>
219 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
232 220 </td>
233 221 <td>
234 <code>${ver.source_ref_parts.commit_id[:6]}</code>
235 </td>
236 <td>
237 ${_('created')} ${h.age_component(ver.updated_on)}
238 </td>
239 <td align="right">
240 % if c.at_version_num == ver_pr:
241 <span id="show-pr-versions" class="btn btn-link" onclick="$('.version-pr').show(); $(this).hide(); return false">${_('Show all versions')}</span>
242 % endif
222 ${h.age_component(ver.updated_on)}
243 223 </td>
244 224 </tr>
245 225 % endfor
246 226
247 ## show comment/inline comments summary
248 227 <tr>
249 <td>
228 <td colspan="5">
229 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none" data-label-text="${_('show changes between versions')}">
230 ${_('show changes between versions')}
231 </button>
250 232 </td>
233 </tr>
251 234
252 <td colspan="4" style="border-top: 1px dashed #dbd9da">
253 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
254 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
255
235 ## show comment/inline comments summary
236 <%def name="comments_summary()">
237 <tr>
238 <td colspan="6" class="comments-summary-td">
256 239
257 240 % if c.at_version:
258 241 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
@@ -264,6 +247,7 b''
264 247 ${_('Comments for this pull request')}:
265 248 % endif
266 249
250
267 251 %if general_comm_count_ver:
268 252 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
269 253 %else:
@@ -285,37 +269,18 b''
285 269 %endif
286 270 </td>
287 271 </tr>
288
289 <tr>
290 <td></td>
291 <td colspan="4">
292 % if c.at_version:
293 <pre>
294 Changed commits:
295 * added: ${len(c.changes.added)}
296 * removed: ${len(c.changes.removed)}
297
298 % if not (c.file_changes.added+c.file_changes.modified+c.file_changes.removed):
299 No file changes found
300 % else:
301 Changed files:
302 %for file_name in c.file_changes.added:
303 * A <a href="#${'a_' + h.FID('', file_name)}">${file_name}</a>
304 %endfor
305 %for file_name in c.file_changes.modified:
306 * M <a href="#${'a_' + h.FID('', file_name)}">${file_name}</a>
307 %endfor
308 %for file_name in c.file_changes.removed:
309 * R ${file_name}
310 %endfor
311 % endif
312 </pre>
313 % endif
314 </td>
315 </tr>
272 </%def>
273 ${comments_summary()}
316 274 </table>
317 275 % else:
276 <div class="input">
318 277 ${_('Pull request versions not available')}.
278 </div>
279 <div>
280 <table>
281 ${comments_summary()}
282 </table>
283 </div>
319 284 % endif
320 285 </div>
321 286 </div>
@@ -426,7 +391,15 b' Changed files:'
426 391 </div>
427 392 </div>
428 393 % endif
394
429 395 <div class="compare_view_commits_title">
396 % if not c.compare_mode:
397
398 % if c.at_version_pos:
399 <h4>
400 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
401 </h4>
402 % endif
430 403
431 404 <div class="pull-left">
432 405 <div class="btn-group">
@@ -453,11 +426,113 b' Changed files:'
453 426 % endif
454 427
455 428 </div>
456
429 % endif
457 430 </div>
458 431
459 432 % if not c.missing_commits:
433 % if c.compare_mode:
434 % if c.at_version:
435 <h4>
436 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
437 </h4>
438
439 <div class="subtitle-compare">
440 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
441 </div>
442
443 <div class="container">
444 <table class="rctable compare_view_commits">
445 <tr>
446 <th></th>
447 <th>${_('Time')}</th>
448 <th>${_('Author')}</th>
449 <th>${_('Commit')}</th>
450 <th></th>
451 <th>${_('Description')}</th>
452 </tr>
453
454 % for c_type, commit in c.commit_changes:
455 % if c_type in ['a', 'r']:
456 <%
457 if c_type == 'a':
458 cc_title = _('Commit added in displayed changes')
459 elif c_type == 'r':
460 cc_title = _('Commit removed in displayed changes')
461 else:
462 cc_title = ''
463 %>
464 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
465 <td>
466 <div class="commit-change-indicator color-${c_type}-border">
467 <div class="commit-change-content color-${c_type} tooltip" title="${cc_title}">
468 ${c_type.upper()}
469 </div>
470 </div>
471 </td>
472 <td class="td-time">
473 ${h.age_component(commit.date)}
474 </td>
475 <td class="td-user">
476 ${base.gravatar_with_user(commit.author, 16)}
477 </td>
478 <td class="td-hash">
479 <code>
480 <a href="${h.url('changeset_home', repo_name=c.target_repo.repo_name, revision=commit.raw_id)}">
481 r${commit.revision}:${h.short_id(commit.raw_id)}
482 </a>
483 ${h.hidden('revisions', commit.raw_id)}
484 </code>
485 </td>
486 <td class="expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}">
487 <div class="show_more_col">
488 <i class="show_more"></i>
489 </div>
490 </td>
491 <td class="mid td-description">
492 <div class="log-container truncate-wrap">
493 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">
494 ${h.urlify_commit_message(commit.message, c.repo_name)}
495 </div>
496 </div>
497 </td>
498 </tr>
499 % endif
500 % endfor
501 </table>
502 </div>
503
504 <script>
505 $('.expand_commit').on('click',function(e){
506 var target_expand = $(this);
507 var cid = target_expand.data('commitId');
508
509 if (target_expand.hasClass('open')){
510 $('#c-'+cid).css({
511 'height': '1.5em',
512 'white-space': 'nowrap',
513 'text-overflow': 'ellipsis',
514 'overflow':'hidden'
515 });
516 target_expand.removeClass('open');
517 }
518 else {
519 $('#c-'+cid).css({
520 'height': 'auto',
521 'white-space': 'pre-line',
522 'text-overflow': 'initial',
523 'overflow':'visible'
524 });
525 target_expand.addClass('open');
526 }
527 });
528 </script>
529
530 % endif
531
532 % else:
460 533 <%include file="/compare/compare_commits.mako" />
534 % endif
535
461 536 <div class="cs_files">
462 537 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
463 538 ${cbdiffs.render_diffset_menu()}
@@ -528,6 +603,107 b' Changed files:'
528 603 }
529 604 }
530 605
606 VersionController = function() {
607 var self = this;
608 this.$verSource = $('input[name=ver_source]');
609 this.$verTarget = $('input[name=ver_target]');
610
611 this.adjustRadioSelectors = function (curNode) {
612 var getVal = function(item) {
613 if (item == 'latest'){
614 return Number.MAX_SAFE_INTEGER
615 }
616 else {
617 return parseInt(item)
618 }
619 };
620
621 var curVal = getVal($(curNode).val());
622 $.each(self.$verSource, function(index, value){
623 var elVal = getVal($(value).val());
624 if(elVal > curVal){
625 $(value).attr('disabled', 'disabled');
626 $(value).removeAttr('checked');
627 }
628 else{
629 $(value).removeAttr('disabled');
630 }
631 });
632
633 self.setLockAction(false, $(curNode).data('verPos'));
634 };
635
636
637 this.attachVersionListener = function () {
638 self.$verTarget.change(function(e){
639 self.adjustRadioSelectors(this)
640 });
641 self.$verSource.change(function(e){
642 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
643 });
644 };
645
646 this.init = function () {
647
648 var curNode = self.$verTarget.filter(':checked');
649 self.adjustRadioSelectors(curNode);
650 self.setLockAction(true);
651 self.attachVersionListener();
652
653 };
654
655 this.setLockAction = function (state, selectedVersion) {
656 if(state){
657 $('#show-version-diff').attr('disabled','disabled')
658 $('#show-version-diff').addClass('disabled')
659 $('#show-version-diff').html($('#show-version-diff').data('labelText'));
660 }
661 else{
662 $('#show-version-diff').removeAttr('disabled');
663 $('#show-version-diff').removeClass('disabled')
664 //$('#show-version-diff').html(_gettext('show changes for v') + selectedVersion)
665 }
666
667 };
668
669 this.showVersionDiff = function(){
670 var target = self.$verTarget.filter(':checked');
671 var source = self.$verSource.filter(':checked');
672
673 if (target.val() && source.val()) {
674 var params = {
675 'pull_request_id': ${c.pull_request.pull_request_id},
676 'repo_name': templateContext.repo_name,
677 'version': target.val(),
678 'from_version': source.val()
679 };
680 window.location = pyroutes.url('pullrequest_show', params)
681 }
682
683 return false;
684 };
685
686 this.toggleVersionView = function (elem) {
687
688 if ($('#show-version-diff').is(':visible')) {
689 $('.version-pr').hide();
690 $('#show-version-diff').hide();
691 $(elem).html($(elem).data('toggleOn'))
692 } else {
693 $('.version-pr').show();
694 $('#show-version-diff').show();
695 $(elem).html($(elem).data('toggleOff'))
696 }
697
698 return false
699 }
700
701 };
702
703 versionController = new VersionController();
704 versionController.init();
705
706
531 707 $(function(){
532 708 ReviewerAutoComplete('user');
533 709 // custom code mirror
General Comments 0
You need to be logged in to leave comments. Login now