##// END OF EJS Templates
pull-requests: fix way how pull-request calculates common ancestors....
marcink -
r4346:4dcd6440 default
parent child Browse files
Show More
@@ -0,0 +1,47 b''
1 # -*- coding: utf-8 -*-
2
3 import logging
4 from sqlalchemy import *
5
6 from alembic.migration import MigrationContext
7 from alembic.operations import Operations
8 from sqlalchemy import BigInteger
9
10 from rhodecode.lib.dbmigrate.versions import _reset_base
11 from rhodecode.model import init_model_encryption
12
13
14 log = logging.getLogger(__name__)
15
16
17 def upgrade(migrate_engine):
18 """
19 Upgrade operations go here.
20 Don't create your own engine; bind migrate_engine to your metadata
21 """
22 _reset_base(migrate_engine)
23 from rhodecode.lib.dbmigrate.schema import db_4_19_0_0 as db
24
25 init_model_encryption(db)
26
27 context = MigrationContext.configure(migrate_engine.connect())
28 op = Operations(context)
29
30 pull_requests = db.PullRequest.__table__
31 with op.batch_alter_table(pull_requests.name) as batch_op:
32 new_column = Column('common_ancestor_id', Unicode(255), nullable=True)
33 batch_op.add_column(new_column)
34
35 pull_request_version = db.PullRequestVersion.__table__
36 with op.batch_alter_table(pull_request_version.name) as batch_op:
37 new_column = Column('common_ancestor_id', Unicode(255), nullable=True)
38 batch_op.add_column(new_column)
39
40
41 def downgrade(migrate_engine):
42 meta = MetaData()
43 meta.bind = migrate_engine
44
45
46 def fixups(models, _SESSION):
47 pass
@@ -48,7 +48,7 b' PYRAMID_SETTINGS = {}'
48 EXTENSIONS = {}
48 EXTENSIONS = {}
49
49
50 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
50 __version__ = ('.'.join((str(each) for each in VERSION[:3])))
51 __dbversion__ = 106 # defines current db version for migrations
51 __dbversion__ = 107 # defines current db version for migrations
52 __platform__ = platform.system()
52 __platform__ = platform.system()
53 __license__ = 'AGPLv3, and Commercial License'
53 __license__ = 'AGPLv3, and Commercial License'
54 __author__ = 'RhodeCode GmbH'
54 __author__ = 'RhodeCode GmbH'
@@ -686,28 +686,9 b' def create_pull_request('
686 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
686 full_source_ref = resolve_ref_or_error(source_ref, source_db_repo)
687 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
687 full_target_ref = resolve_ref_or_error(target_ref, target_db_repo)
688
688
689 source_scm = source_db_repo.scm_instance()
690 target_scm = target_db_repo.scm_instance()
691
692 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
689 source_commit = get_commit_or_error(full_source_ref, source_db_repo)
693 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
690 target_commit = get_commit_or_error(full_target_ref, target_db_repo)
694
691
695 ancestor = source_scm.get_common_ancestor(
696 source_commit.raw_id, target_commit.raw_id, target_scm)
697 if not ancestor:
698 raise JSONRPCError('no common ancestor found')
699
700 # recalculate target ref based on ancestor
701 target_ref_type, target_ref_name, __ = full_target_ref.split(':')
702 full_target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
703
704 commit_ranges = target_scm.compare(
705 target_commit.raw_id, source_commit.raw_id, source_scm,
706 merge=True, pre_load=[])
707
708 if not commit_ranges:
709 raise JSONRPCError('no commits found')
710
711 reviewer_objects = Optional.extract(reviewers) or []
692 reviewer_objects = Optional.extract(reviewers) or []
712
693
713 # serialize and validate passed in given reviewers
694 # serialize and validate passed in given reviewers
@@ -727,16 +708,16 b' def create_pull_request('
727 PullRequestModel().get_reviewer_functions()
708 PullRequestModel().get_reviewer_functions()
728
709
729 # recalculate reviewers logic, to make sure we can validate this
710 # recalculate reviewers logic, to make sure we can validate this
730 reviewer_rules = get_default_reviewers_data(
711 default_reviewers_data = get_default_reviewers_data(
731 owner, source_db_repo,
712 owner, source_db_repo,
732 source_commit, target_db_repo, target_commit)
713 source_commit, target_db_repo, target_commit)
733
714
734 # now MERGE our given with the calculated
715 # now MERGE our given with the calculated
735 reviewer_objects = reviewer_rules['reviewers'] + reviewer_objects
716 reviewer_objects = default_reviewers_data['reviewers'] + reviewer_objects
736
717
737 try:
718 try:
738 reviewers = validate_default_reviewers(
719 reviewers = validate_default_reviewers(
739 reviewer_objects, reviewer_rules)
720 reviewer_objects, default_reviewers_data)
740 except ValueError as e:
721 except ValueError as e:
741 raise JSONRPCError('Reviewers Validation: {}'.format(e))
722 raise JSONRPCError('Reviewers Validation: {}'.format(e))
742
723
@@ -748,6 +729,24 b' def create_pull_request('
748 source_ref=title_source_ref,
729 source_ref=title_source_ref,
749 target=target_repo
730 target=target_repo
750 )
731 )
732
733 diff_info = default_reviewers_data['diff_info']
734 common_ancestor_id = diff_info['ancestor']
735 commits = diff_info['commits']
736
737 if not common_ancestor_id:
738 raise JSONRPCError('no common ancestor found')
739
740 if not commits:
741 raise JSONRPCError('no commits found')
742
743 # NOTE(marcink): reversed is consistent with how we open it in the WEB interface
744 revisions = [commit.raw_id for commit in reversed(commits)]
745
746 # recalculate target ref based on ancestor
747 target_ref_type, target_ref_name, __ = full_target_ref.split(':')
748 full_target_ref = ':'.join((target_ref_type, target_ref_name, common_ancestor_id))
749
751 # fetch renderer, if set fallback to plain in case of PR
750 # fetch renderer, if set fallback to plain in case of PR
752 rc_config = SettingsModel().get_all_settings()
751 rc_config = SettingsModel().get_all_settings()
753 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
752 default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain')
@@ -760,12 +759,13 b' def create_pull_request('
760 source_ref=full_source_ref,
759 source_ref=full_source_ref,
761 target_repo=target_repo,
760 target_repo=target_repo,
762 target_ref=full_target_ref,
761 target_ref=full_target_ref,
763 revisions=[commit.raw_id for commit in reversed(commit_ranges)],
762 common_ancestor_id=common_ancestor_id,
763 revisions=revisions,
764 reviewers=reviewers,
764 reviewers=reviewers,
765 title=title,
765 title=title,
766 description=description,
766 description=description,
767 description_renderer=description_renderer,
767 description_renderer=description_renderer,
768 reviewer_data=reviewer_rules,
768 reviewer_data=default_reviewers_data,
769 auth_user=apiuser
769 auth_user=apiuser
770 )
770 )
771
771
@@ -484,7 +484,7 b' class TestCompareView(object):'
484
484
485 # outgoing commits between those commits
485 # outgoing commits between those commits
486 compare_page = ComparePage(response)
486 compare_page = ComparePage(response)
487 compare_page.contains_commits(commits=[commit1], ancestors=[commit0])
487 compare_page.contains_commits(commits=[commit1])
488
488
489 def test_errors_when_comparing_unknown_source_repo(self, backend):
489 def test_errors_when_comparing_unknown_source_repo(self, backend):
490 repo = backend.repo
490 repo = backend.repo
@@ -641,6 +641,7 b' class ComparePage(AssertResponse):'
641 self.contains_one_link(
641 self.contains_one_link(
642 'r%s:%s' % (commit.idx, commit.short_id),
642 'r%s:%s' % (commit.idx, commit.short_id),
643 self._commit_url(commit))
643 self._commit_url(commit))
644
644 if ancestors:
645 if ancestors:
645 response.mustcontain('Ancestor')
646 response.mustcontain('Ancestor')
646 for ancestor in ancestors:
647 for ancestor in ancestors:
@@ -20,6 +20,9 b''
20
20
21 from rhodecode.lib import helpers as h
21 from rhodecode.lib import helpers as h
22 from rhodecode.lib.utils2 import safe_int
22 from rhodecode.lib.utils2 import safe_int
23 from rhodecode.model.pull_request import get_diff_info
24
25 REVIEWER_API_VERSION = 'V3'
23
26
24
27
25 def reviewer_as_json(user, reasons=None, mandatory=False, rules=None, user_group=None):
28 def reviewer_as_json(user, reasons=None, mandatory=False, rules=None, user_group=None):
@@ -47,15 +50,20 b' def reviewer_as_json(user, reasons=None,'
47
50
48 def get_default_reviewers_data(
51 def get_default_reviewers_data(
49 current_user, source_repo, source_commit, target_repo, target_commit):
52 current_user, source_repo, source_commit, target_repo, target_commit):
53 """
54 Return json for default reviewers of a repository
55 """
50
56
51 """ Return json for default reviewers of a repository """
57 diff_info = get_diff_info(
58 source_repo, source_commit.raw_id, target_repo, target_commit.raw_id)
52
59
53 reasons = ['Default reviewer', 'Repository owner']
60 reasons = ['Default reviewer', 'Repository owner']
54 json_reviewers = [reviewer_as_json(
61 json_reviewers = [reviewer_as_json(
55 user=target_repo.user, reasons=reasons, mandatory=False, rules=None)]
62 user=target_repo.user, reasons=reasons, mandatory=False, rules=None)]
56
63
57 return {
64 return {
58 'api_ver': 'v1', # define version for later possible schema upgrade
65 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade
66 'diff_info': diff_info,
59 'reviewers': json_reviewers,
67 'reviewers': json_reviewers,
60 'rules': {},
68 'rules': {},
61 'rules_data': {},
69 'rules_data': {},
@@ -40,12 +40,12 b' from rhodecode.lib.auth import ('
40 NotAnonymous, CSRFRequired)
40 NotAnonymous, CSRFRequired)
41 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
41 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
42 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
42 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
43 from rhodecode.lib.vcs.exceptions import (CommitDoesNotExistError,
43 from rhodecode.lib.vcs.exceptions import (
44 RepositoryRequirementError, EmptyRepositoryError)
44 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
45 from rhodecode.model.changeset_status import ChangesetStatusModel
45 from rhodecode.model.changeset_status import ChangesetStatusModel
46 from rhodecode.model.comment import CommentsModel
46 from rhodecode.model.comment import CommentsModel
47 from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion,
47 from rhodecode.model.db import (
48 ChangesetComment, ChangesetStatus, Repository)
48 func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository)
49 from rhodecode.model.forms import PullRequestForm
49 from rhodecode.model.forms import PullRequestForm
50 from rhodecode.model.meta import Session
50 from rhodecode.model.meta import Session
51 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
51 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
@@ -210,10 +210,12 b' class RepoPullRequestsView(RepoAppView, '
210 return caching_enabled
210 return caching_enabled
211
211
212 def _get_diffset(self, source_repo_name, source_repo,
212 def _get_diffset(self, source_repo_name, source_repo,
213 ancestor_commit,
213 source_ref_id, target_ref_id,
214 source_ref_id, target_ref_id,
214 target_commit, source_commit, diff_limit, file_limit,
215 target_commit, source_commit, diff_limit, file_limit,
215 fulldiff, hide_whitespace_changes, diff_context):
216 fulldiff, hide_whitespace_changes, diff_context):
216
217
218 target_ref_id = ancestor_commit.raw_id
217 vcs_diff = PullRequestModel().get_diff(
219 vcs_diff = PullRequestModel().get_diff(
218 source_repo, source_ref_id, target_ref_id,
220 source_repo, source_ref_id, target_ref_id,
219 hide_whitespace_changes, diff_context)
221 hide_whitespace_changes, diff_context)
@@ -278,6 +280,7 b' class RepoPullRequestsView(RepoAppView, '
278 _new_state = {
280 _new_state = {
279 'created': PullRequest.STATE_CREATED,
281 'created': PullRequest.STATE_CREATED,
280 }.get(self.request.GET.get('force_state'))
282 }.get(self.request.GET.get('force_state'))
283
281 if c.is_super_admin and _new_state:
284 if c.is_super_admin and _new_state:
282 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
285 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
283 h.flash(
286 h.flash(
@@ -557,7 +560,8 b' class RepoPullRequestsView(RepoAppView, '
557 source_scm,
560 source_scm,
558 target_commit,
561 target_commit,
559 target_ref_id,
562 target_ref_id,
560 target_scm, maybe_unreachable=maybe_unreachable)
563 target_scm,
564 maybe_unreachable=maybe_unreachable)
561
565
562 # register our commit range
566 # register our commit range
563 for comm in commit_cache.values():
567 for comm in commit_cache.values():
@@ -591,11 +595,10 b' class RepoPullRequestsView(RepoAppView, '
591 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
595 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
592 if not force_recache and has_proper_diff_cache:
596 if not force_recache and has_proper_diff_cache:
593 c.diffset = cached_diff['diff']
597 c.diffset = cached_diff['diff']
594 (ancestor_commit, commit_cache, missing_requirements,
595 source_commit, target_commit) = cached_diff['commits']
596 else:
598 else:
597 c.diffset = self._get_diffset(
599 c.diffset = self._get_diffset(
598 c.source_repo.repo_name, commits_source_repo,
600 c.source_repo.repo_name, commits_source_repo,
601 c.ancestor_commit,
599 source_ref_id, target_ref_id,
602 source_ref_id, target_ref_id,
600 target_commit, source_commit,
603 target_commit, source_commit,
601 diff_limit, file_limit, c.fulldiff,
604 diff_limit, file_limit, c.fulldiff,
@@ -675,8 +678,10 b' class RepoPullRequestsView(RepoAppView, '
675
678
676 # calculate the diff for commits between versions
679 # calculate the diff for commits between versions
677 c.commit_changes = []
680 c.commit_changes = []
678 mark = lambda cs, fw: list(
681
679 h.itertools.izip_longest([], cs, fillvalue=fw))
682 def mark(cs, fw):
683 return list(h.itertools.izip_longest([], cs, fillvalue=fw))
684
680 for c_type, raw_id in mark(commit_changes.added, 'a') \
685 for c_type, raw_id in mark(commit_changes.added, 'a') \
681 + mark(commit_changes.removed, 'r') \
686 + mark(commit_changes.removed, 'r') \
682 + mark(commit_changes.common, 'c'):
687 + mark(commit_changes.common, 'c'):
@@ -739,13 +744,14 b' class RepoPullRequestsView(RepoAppView, '
739 except RepositoryRequirementError:
744 except RepositoryRequirementError:
740 log.warning('Failed to get all required data from repo', exc_info=True)
745 log.warning('Failed to get all required data from repo', exc_info=True)
741 missing_requirements = True
746 missing_requirements = True
742 ancestor_commit = None
747
748 pr_ancestor_id = pull_request_at_ver.common_ancestor_id
749
743 try:
750 try:
744 ancestor_id = source_scm.get_common_ancestor(
751 ancestor_commit = source_scm.get_commit(pr_ancestor_id)
745 source_commit.raw_id, target_commit.raw_id, target_scm)
746 ancestor_commit = source_scm.get_commit(ancestor_id)
747 except Exception:
752 except Exception:
748 ancestor_commit = None
753 ancestor_commit = None
754
749 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
755 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
750
756
751 def assure_not_empty_repo(self):
757 def assure_not_empty_repo(self):
@@ -949,6 +955,7 b' class RepoPullRequestsView(RepoAppView, '
949 target_repo = _form['target_repo']
955 target_repo = _form['target_repo']
950 target_ref = _form['target_ref']
956 target_ref = _form['target_ref']
951 commit_ids = _form['revisions'][::-1]
957 commit_ids = _form['revisions'][::-1]
958 common_ancestor_id = _form['common_ancestor']
952
959
953 # find the ancestor for this pr
960 # find the ancestor for this pr
954 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
961 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
@@ -1035,6 +1042,7 b' class RepoPullRequestsView(RepoAppView, '
1035 target_repo=target_repo,
1042 target_repo=target_repo,
1036 target_ref=target_ref,
1043 target_ref=target_ref,
1037 revisions=commit_ids,
1044 revisions=commit_ids,
1045 common_ancestor_id=common_ancestor_id,
1038 reviewers=reviewers,
1046 reviewers=reviewers,
1039 title=pullrequest_title,
1047 title=pullrequest_title,
1040 description=description,
1048 description=description,
@@ -54,8 +54,20 b' class RepoReviewRulesView(RepoAppView):'
54 renderer='json_ext')
54 renderer='json_ext')
55 def repo_default_reviewers_data(self):
55 def repo_default_reviewers_data(self):
56 self.load_default_context()
56 self.load_default_context()
57 target_repo_name = self.request.GET.get('target_repo', self.db_repo.repo_name)
57
58 request = self.request
59 source_repo = self.db_repo
60 source_repo_name = source_repo.repo_name
61 target_repo_name = request.GET.get('target_repo', source_repo_name)
58 target_repo = Repository.get_by_repo_name(target_repo_name)
62 target_repo = Repository.get_by_repo_name(target_repo_name)
63
64 source_ref = request.GET['source_ref']
65 target_ref = request.GET['target_ref']
66 source_commit = source_repo.get_commit(source_ref)
67 target_commit = target_repo.get_commit(target_ref)
68
69 current_user = request.user.get_instance()
59 review_data = get_default_reviewers_data(
70 review_data = get_default_reviewers_data(
60 self.db_repo.user, None, None, target_repo, None)
71 current_user, source_repo, source_commit, target_repo, target_commit)
72
61 return review_data
73 return review_data
@@ -580,6 +580,9 b' class GitRepository(BaseRepository):'
580 return len(self.commit_ids)
580 return len(self.commit_ids)
581
581
582 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
582 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
583 log.debug('Calculating common ancestor between %sc1:%s and %sc2:%s',
584 self, commit_id1, repo2, commit_id2)
585
583 if commit_id1 == commit_id2:
586 if commit_id1 == commit_id2:
584 return commit_id1
587 return commit_id1
585
588
@@ -600,6 +603,8 b' class GitRepository(BaseRepository):'
600 ['merge-base', commit_id1, commit_id2])
603 ['merge-base', commit_id1, commit_id2])
601 ancestor_id = re.findall(r'[0-9a-fA-F]{40}', output)[0]
604 ancestor_id = re.findall(r'[0-9a-fA-F]{40}', output)[0]
602
605
606 log.debug('Found common ancestor with sha: %s', ancestor_id)
607
603 return ancestor_id
608 return ancestor_id
604
609
605 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
610 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
@@ -297,13 +297,20 b' class MercurialRepository(BaseRepository'
297 return update_cache
297 return update_cache
298
298
299 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
299 def get_common_ancestor(self, commit_id1, commit_id2, repo2):
300 log.debug('Calculating common ancestor between %sc1:%s and %sc2:%s',
301 self, commit_id1, repo2, commit_id2)
302
300 if commit_id1 == commit_id2:
303 if commit_id1 == commit_id2:
301 return commit_id1
304 return commit_id1
302
305
303 ancestors = self._remote.revs_from_revspec(
306 ancestors = self._remote.revs_from_revspec(
304 "ancestor(id(%s), id(%s))", commit_id1, commit_id2,
307 "ancestor(id(%s), id(%s))", commit_id1, commit_id2,
305 other_path=repo2.path)
308 other_path=repo2.path)
306 return repo2[ancestors[0]].raw_id if ancestors else None
309
310 ancestor_id = repo2[ancestors[0]].raw_id if ancestors else None
311
312 log.debug('Found common ancestor with sha: %s', ancestor_id)
313 return ancestor_id
307
314
308 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
315 def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None):
309 if commit_id1 == commit_id2:
316 if commit_id1 == commit_id2:
@@ -3989,6 +3989,8 b' class _PullRequestBase(BaseModel):'
3989 _revisions = Column(
3989 _revisions = Column(
3990 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3990 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql'))
3991
3991
3992 common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True)
3993
3992 @declared_attr
3994 @declared_attr
3993 def source_repo_id(cls):
3995 def source_repo_id(cls):
3994 # TODO: dan: rename column to source_repo_id
3996 # TODO: dan: rename column to source_repo_id
@@ -4302,7 +4304,7 b' class PullRequest(Base, _PullRequestBase'
4302 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4304 attrs.source_ref_parts = pull_request_obj.source_ref_parts
4303 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4305 attrs.target_ref_parts = pull_request_obj.target_ref_parts
4304 attrs.revisions = pull_request_obj.revisions
4306 attrs.revisions = pull_request_obj.revisions
4305
4307 attrs.common_ancestor_id = pull_request_obj.common_ancestor_id
4306 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4308 attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref
4307 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4309 attrs.reviewer_data = org_pull_request_obj.reviewer_data
4308 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
4310 attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json
@@ -35,6 +35,7 b' import collections'
35 from pyramid import compat
35 from pyramid import compat
36 from pyramid.threadlocal import get_current_request
36 from pyramid.threadlocal import get_current_request
37
37
38 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.translation import lazy_ugettext
39 from rhodecode.translation import lazy_ugettext
39 from rhodecode.lib import helpers as h, hooks_utils, diffs
40 from rhodecode.lib import helpers as h, hooks_utils, diffs
40 from rhodecode.lib import audit_logger
41 from rhodecode.lib import audit_logger
@@ -82,6 +83,126 b' class UpdateResponse(object):'
82 self.target_changed = target_changed
83 self.target_changed = target_changed
83
84
84
85
86 def get_diff_info(
87 source_repo, source_ref, target_repo, target_ref, get_authors=False,
88 get_commit_authors=True):
89 """
90 Calculates detailed diff information for usage in preview of creation of a pull-request.
91 This is also used for default reviewers logic
92 """
93
94 source_scm = source_repo.scm_instance()
95 target_scm = target_repo.scm_instance()
96
97 ancestor_id = target_scm.get_common_ancestor(target_ref, source_ref, source_scm)
98 if not ancestor_id:
99 raise ValueError(
100 'cannot calculate diff info without a common ancestor. '
101 'Make sure both repositories are related, and have a common forking commit.')
102
103 # case here is that want a simple diff without incoming commits,
104 # previewing what will be merged based only on commits in the source.
105 log.debug('Using ancestor %s as source_ref instead of %s',
106 ancestor_id, source_ref)
107
108 # source of changes now is the common ancestor
109 source_commit = source_scm.get_commit(commit_id=ancestor_id)
110 # target commit becomes the source ref as it is the last commit
111 # for diff generation this logic gives proper diff
112 target_commit = source_scm.get_commit(commit_id=source_ref)
113
114 vcs_diff = \
115 source_scm.get_diff(commit1=source_commit, commit2=target_commit,
116 ignore_whitespace=False, context=3)
117
118 diff_processor = diffs.DiffProcessor(
119 vcs_diff, format='newdiff', diff_limit=None,
120 file_limit=None, show_full_diff=True)
121
122 _parsed = diff_processor.prepare()
123
124 all_files = []
125 all_files_changes = []
126 changed_lines = {}
127 stats = [0, 0]
128 for f in _parsed:
129 all_files.append(f['filename'])
130 all_files_changes.append({
131 'filename': f['filename'],
132 'stats': f['stats']
133 })
134 stats[0] += f['stats']['added']
135 stats[1] += f['stats']['deleted']
136
137 changed_lines[f['filename']] = []
138 if len(f['chunks']) < 2:
139 continue
140 # first line is "context" information
141 for chunks in f['chunks'][1:]:
142 for chunk in chunks['lines']:
143 if chunk['action'] not in ('del', 'mod'):
144 continue
145 changed_lines[f['filename']].append(chunk['old_lineno'])
146
147 commit_authors = []
148 user_counts = {}
149 email_counts = {}
150 author_counts = {}
151 _commit_cache = {}
152
153 commits = []
154 if get_commit_authors:
155 commits = target_scm.compare(
156 target_ref, source_ref, source_scm, merge=True,
157 pre_load=["author"])
158
159 for commit in commits:
160 user = User.get_from_cs_author(commit.author)
161 if user and user not in commit_authors:
162 commit_authors.append(user)
163
164 # lines
165 if get_authors:
166 target_commit = source_repo.get_commit(ancestor_id)
167
168 for fname, lines in changed_lines.items():
169 try:
170 node = target_commit.get_node(fname)
171 except Exception:
172 continue
173
174 if not isinstance(node, FileNode):
175 continue
176
177 for annotation in node.annotate:
178 line_no, commit_id, get_commit_func, line_text = annotation
179 if line_no in lines:
180 if commit_id not in _commit_cache:
181 _commit_cache[commit_id] = get_commit_func()
182 commit = _commit_cache[commit_id]
183 author = commit.author
184 email = commit.author_email
185 user = User.get_from_cs_author(author)
186 if user:
187 user_counts[user] = user_counts.get(user, 0) + 1
188 author_counts[author] = author_counts.get(author, 0) + 1
189 email_counts[email] = email_counts.get(email, 0) + 1
190
191 return {
192 'commits': commits,
193 'files': all_files_changes,
194 'stats': stats,
195 'ancestor': ancestor_id,
196 # original authors of modified files
197 'original_authors': {
198 'users': user_counts,
199 'authors': author_counts,
200 'emails': email_counts,
201 },
202 'commit_authors': commit_authors
203 }
204
205
85 class PullRequestModel(BaseModel):
206 class PullRequestModel(BaseModel):
86
207
87 cls = PullRequest
208 cls = PullRequest
@@ -453,6 +574,7 b' class PullRequestModel(BaseModel):'
453
574
454 def create(self, created_by, source_repo, source_ref, target_repo,
575 def create(self, created_by, source_repo, source_ref, target_repo,
455 target_ref, revisions, reviewers, title, description=None,
576 target_ref, revisions, reviewers, title, description=None,
577 common_ancestor_id=None,
456 description_renderer=None,
578 description_renderer=None,
457 reviewer_data=None, translator=None, auth_user=None):
579 reviewer_data=None, translator=None, auth_user=None):
458 translator = translator or get_current_request().translate
580 translator = translator or get_current_request().translate
@@ -474,6 +596,8 b' class PullRequestModel(BaseModel):'
474 pull_request.author = created_by_user
596 pull_request.author = created_by_user
475 pull_request.reviewer_data = reviewer_data
597 pull_request.reviewer_data = reviewer_data
476 pull_request.pull_request_state = pull_request.STATE_CREATING
598 pull_request.pull_request_state = pull_request.STATE_CREATING
599 pull_request.common_ancestor_id = common_ancestor_id
600
477 Session().add(pull_request)
601 Session().add(pull_request)
478 Session().flush()
602 Session().flush()
479
603
@@ -805,8 +929,17 b' class PullRequestModel(BaseModel):'
805 target_commit.raw_id, source_commit.raw_id, source_repo, merge=True,
929 target_commit.raw_id, source_commit.raw_id, source_repo, merge=True,
806 pre_load=pre_load)
930 pre_load=pre_load)
807
931
808 ancestor_commit_id = source_repo.get_common_ancestor(
932 target_ref = target_commit.raw_id
809 source_commit.raw_id, target_commit.raw_id, target_repo)
933 source_ref = source_commit.raw_id
934 ancestor_commit_id = target_repo.get_common_ancestor(
935 target_ref, source_ref, source_repo)
936
937 if not ancestor_commit_id:
938 raise ValueError(
939 'cannot calculate diff info without a common ancestor. '
940 'Make sure both repositories are related, and have a common forking commit.')
941
942 pull_request.common_ancestor_id = ancestor_commit_id
810
943
811 pull_request.source_ref = '%s:%s:%s' % (
944 pull_request.source_ref = '%s:%s:%s' % (
812 source_ref_type, source_ref_name, source_commit.raw_id)
945 source_ref_type, source_ref_name, source_commit.raw_id)
@@ -913,6 +1046,7 b' class PullRequestModel(BaseModel):'
913 version.reviewer_data = pull_request.reviewer_data
1046 version.reviewer_data = pull_request.reviewer_data
914
1047
915 version.revisions = pull_request.revisions
1048 version.revisions = pull_request.revisions
1049 version.common_ancestor_id = pull_request.common_ancestor_id
916 version.pull_request = pull_request
1050 version.pull_request = pull_request
917 Session().add(version)
1051 Session().add(version)
918 Session().flush()
1052 Session().flush()
@@ -467,9 +467,9 b' ul.auth_plugins {'
467 #pr_open_message {
467 #pr_open_message {
468 border: @border-thickness solid #fff;
468 border: @border-thickness solid #fff;
469 border-radius: @border-radius;
469 border-radius: @border-radius;
470 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
471 text-align: left;
470 text-align: left;
472 overflow: hidden;
471 overflow: hidden;
472 white-space: pre-line;
473 }
473 }
474
474
475 .pr-details-title {
475 .pr-details-title {
@@ -1796,7 +1796,7 b' table.integrations {'
1796 }
1796 }
1797
1797
1798 div.ancestor {
1798 div.ancestor {
1799 line-height: 33px;
1799
1800 }
1800 }
1801
1801
1802 .cs_icon_td input[type="checkbox"] {
1802 .cs_icon_td input[type="checkbox"] {
@@ -205,7 +205,9 b' select.select2{height:28px;visibility:hi'
205 font-family: @text-regular;
205 font-family: @text-regular;
206 color: @grey2;
206 color: @grey2;
207 cursor: pointer;
207 cursor: pointer;
208 white-space: nowrap;
208 }
209 }
210
209 &.select2-result-with-children {
211 &.select2-result-with-children {
210
212
211 .select2-result-label {
213 .select2-result-label {
@@ -220,6 +222,7 b' select.select2{height:28px;visibility:hi'
220 font-family: @text-regular;
222 font-family: @text-regular;
221 color: @grey2;
223 color: @grey2;
222 cursor: pointer;
224 cursor: pointer;
225 white-space: nowrap;
223 }
226 }
224 }
227 }
225 }
228 }
@@ -22,7 +22,7 b' var _gettext = function (s) {'
22 if (_TM.hasOwnProperty(s)) {
22 if (_TM.hasOwnProperty(s)) {
23 return _TM[s];
23 return _TM[s];
24 }
24 }
25 i18nLog.error(
25 i18nLog.warning(
26 'String `' + s + '` was requested but cannot be ' +
26 'String `' + s + '` was requested but cannot be ' +
27 'found in translation table');
27 'found in translation table');
28 return s
28 return s
@@ -34,3 +34,5 b' var _ngettext = function (singular, plur'
34 }
34 }
35 return _gettext(plural)
35 return _gettext(plural)
36 };
36 };
37
38
@@ -75,12 +75,12 b' var getTitleAndDescription = function(so'
75 var desc = '';
75 var desc = '';
76
76
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
78 var rawMessage = $(value).find('td.td-description .message').data('messageRaw').toString();
78 var rawMessage = value['message'];
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
80 });
80 });
81 // only 1 commit, use commit message as title
81 // only 1 commit, use commit message as title
82 if (elements.length === 1) {
82 if (elements.length === 1) {
83 var rawMessage = $(elements[0]).find('td.td-description .message').data('messageRaw').toString();
83 var rawMessage = elements[0]['message'];
84 title = rawMessage.split('\n')[0];
84 title = rawMessage.split('\n')[0];
85 }
85 }
86 else {
86 else {
@@ -92,7 +92,6 b' var getTitleAndDescription = function(so'
92 };
92 };
93
93
94
94
95
96 ReviewersController = function () {
95 ReviewersController = function () {
97 var self = this;
96 var self = this;
98 this.$reviewRulesContainer = $('#review_rules');
97 this.$reviewRulesContainer = $('#review_rules');
@@ -100,28 +99,35 b' ReviewersController = function () {'
100 this.forbidReviewUsers = undefined;
99 this.forbidReviewUsers = undefined;
101 this.$reviewMembers = $('#review_members');
100 this.$reviewMembers = $('#review_members');
102 this.currentRequest = null;
101 this.currentRequest = null;
102 this.diffData = null;
103 //dummy handler, we might register our own later
104 this.diffDataHandler = function(data){};
103
105
104 this.defaultForbidReviewUsers = function() {
106 this.defaultForbidReviewUsers = function () {
105 return [
107 return [
106 {'username': 'default',
108 {
107 'user_id': templateContext.default_user.user_id}
109 'username': 'default',
110 'user_id': templateContext.default_user.user_id
111 }
108 ];
112 ];
109 };
113 };
110
114
111 this.hideReviewRules = function() {
115 this.hideReviewRules = function () {
112 self.$reviewRulesContainer.hide();
116 self.$reviewRulesContainer.hide();
113 };
117 };
114
118
115 this.showReviewRules = function() {
119 this.showReviewRules = function () {
116 self.$reviewRulesContainer.show();
120 self.$reviewRulesContainer.show();
117 };
121 };
118
122
119 this.addRule = function(ruleText) {
123 this.addRule = function (ruleText) {
120 self.showReviewRules();
124 self.showReviewRules();
121 return '<div>- {0}</div>'.format(ruleText)
125 return '<div>- {0}</div>'.format(ruleText)
122 };
126 };
123
127
124 this.loadReviewRules = function(data) {
128 this.loadReviewRules = function (data) {
129 self.diffData = data;
130
125 // reset forbidden Users
131 // reset forbidden Users
126 this.forbidReviewUsers = self.defaultForbidReviewUsers();
132 this.forbidReviewUsers = self.defaultForbidReviewUsers();
127
133
@@ -141,7 +147,7 b' ReviewersController = function () {'
141 if (data.rules.voting < 0) {
147 if (data.rules.voting < 0) {
142 self.$rulesList.append(
148 self.$rulesList.append(
143 self.addRule(
149 self.addRule(
144 _gettext('All individual reviewers must vote.'))
150 _gettext('All individual reviewers must vote.'))
145 )
151 )
146 } else if (data.rules.voting === 1) {
152 } else if (data.rules.voting === 1) {
147 self.$rulesList.append(
153 self.$rulesList.append(
@@ -158,7 +164,7 b' ReviewersController = function () {'
158 }
164 }
159
165
160 if (data.rules.voting_groups !== undefined) {
166 if (data.rules.voting_groups !== undefined) {
161 $.each(data.rules.voting_groups, function(index, rule_data) {
167 $.each(data.rules.voting_groups, function (index, rule_data) {
162 self.$rulesList.append(
168 self.$rulesList.append(
163 self.addRule(rule_data.text)
169 self.addRule(rule_data.text)
164 )
170 )
@@ -188,7 +194,7 b' ReviewersController = function () {'
188 if (data.rules.forbid_commit_author_to_review) {
194 if (data.rules.forbid_commit_author_to_review) {
189
195
190 if (data.rules_data.forbidden_users) {
196 if (data.rules_data.forbidden_users) {
191 $.each(data.rules_data.forbidden_users, function(index, member_data) {
197 $.each(data.rules_data.forbidden_users, function (index, member_data) {
192 self.forbidReviewUsers.push(member_data)
198 self.forbidReviewUsers.push(member_data)
193 });
199 });
194
200
@@ -203,11 +209,10 b' ReviewersController = function () {'
203 return self.forbidReviewUsers
209 return self.forbidReviewUsers
204 };
210 };
205
211
206 this.loadDefaultReviewers = function(sourceRepo, sourceRef, targetRepo, targetRef) {
212 this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) {
207
213
208 if (self.currentRequest) {
214 if (self.currentRequest) {
209 // make sure we cleanup old running requests before triggering this
215 // make sure we cleanup old running requests before triggering this again
210 // again
211 self.currentRequest.abort();
216 self.currentRequest.abort();
212 }
217 }
213
218
@@ -218,6 +223,9 b' ReviewersController = function () {'
218 prButtonLock(true, null, 'reviewers');
223 prButtonLock(true, null, 'reviewers');
219 $('#user').hide(); // hide user autocomplete before load
224 $('#user').hide(); // hide user autocomplete before load
220
225
226 // lock PR button, so we cannot send PR before it's calculated
227 prButtonLock(true, _gettext('Loading diff ...'), 'compare');
228
221 if (sourceRef.length !== 3 || targetRef.length !== 3) {
229 if (sourceRef.length !== 3 || targetRef.length !== 3) {
222 // don't load defaults in case we're missing some refs...
230 // don't load defaults in case we're missing some refs...
223 $('.calculate-reviewers').hide();
231 $('.calculate-reviewers').hide();
@@ -225,58 +233,80 b' ReviewersController = function () {'
225 }
233 }
226
234
227 var url = pyroutes.url('repo_default_reviewers_data',
235 var url = pyroutes.url('repo_default_reviewers_data',
228 {
236 {
229 'repo_name': templateContext.repo_name,
237 'repo_name': templateContext.repo_name,
230 'source_repo': sourceRepo,
238 'source_repo': sourceRepo,
231 'source_ref': sourceRef[2],
239 'source_ref': sourceRef[2],
232 'target_repo': targetRepo,
240 'target_repo': targetRepo,
233 'target_ref': targetRef[2]
241 'target_ref': targetRef[2]
234 });
242 });
235
243
236 self.currentRequest = $.get(url)
244 self.currentRequest = $.ajax({
237 .done(function(data) {
245 url: url,
246 headers: {'X-PARTIAL-XHR': true},
247 type: 'GET',
248 success: function (data) {
249
238 self.currentRequest = null;
250 self.currentRequest = null;
239
251
240 // review rules
252 // review rules
241 self.loadReviewRules(data);
253 self.loadReviewRules(data);
254 self.handleDiffData(data["diff_info"]);
242
255
243 for (var i = 0; i < data.reviewers.length; i++) {
256 for (var i = 0; i < data.reviewers.length; i++) {
244 var reviewer = data.reviewers[i];
257 var reviewer = data.reviewers[i];
245 self.addReviewMember(
258 self.addReviewMember(reviewer, reviewer.reasons, reviewer.mandatory);
246 reviewer, reviewer.reasons, reviewer.mandatory);
247 }
259 }
248 $('.calculate-reviewers').hide();
260 $('.calculate-reviewers').hide();
249 prButtonLock(false, null, 'reviewers');
261 prButtonLock(false, null, 'reviewers');
250 $('#user').show(); // show user autocomplete after load
262 $('#user').show(); // show user autocomplete after load
251 });
263
264 var commitElements = data["diff_info"]['commits'];
265 if (commitElements.length === 0) {
266 prButtonLock(true, _gettext('no commits'), 'all');
267
268 } else {
269 // un-lock PR button, so we cannot send PR before it's calculated
270 prButtonLock(false, null, 'compare');
271 }
272
273 },
274 error: function (jqXHR, textStatus, errorThrown) {
275 var prefix = "Loading diff and reviewers failed\n"
276 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
277 ajaxErrorSwal(message);
278 }
279 });
280
252 };
281 };
253
282
254 // check those, refactor
283 // check those, refactor
255 this.removeReviewMember = function(reviewer_id, mark_delete) {
284 this.removeReviewMember = function (reviewer_id, mark_delete) {
256 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
285 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
257
286
258 if(typeof(mark_delete) === undefined){
287 if (typeof (mark_delete) === undefined) {
259 mark_delete = false;
288 mark_delete = false;
260 }
289 }
261
290
262 if(mark_delete === true){
291 if (mark_delete === true) {
263 if (reviewer){
292 if (reviewer) {
264 // now delete the input
293 // now delete the input
265 $('#reviewer_{0} input'.format(reviewer_id)).remove();
294 $('#reviewer_{0} input'.format(reviewer_id)).remove();
266 // mark as to-delete
295 // mark as to-delete
267 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
296 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
268 obj.addClass('to-delete');
297 obj.addClass('to-delete');
269 obj.css({"text-decoration":"line-through", "opacity": 0.5});
298 obj.css({"text-decoration": "line-through", "opacity": 0.5});
270 }
299 }
271 }
300 } else {
272 else{
273 $('#reviewer_{0}'.format(reviewer_id)).remove();
301 $('#reviewer_{0}'.format(reviewer_id)).remove();
274 }
302 }
275 };
303 };
276 this.reviewMemberEntry = function() {
304
305 this.reviewMemberEntry = function () {
277
306
278 };
307 };
279 this.addReviewMember = function(reviewer_obj, reasons, mandatory) {
308
309 this.addReviewMember = function (reviewer_obj, reasons, mandatory) {
280 var members = self.$reviewMembers.get(0);
310 var members = self.$reviewMembers.get(0);
281 var id = reviewer_obj.user_id;
311 var id = reviewer_obj.user_id;
282 var username = reviewer_obj.username;
312 var username = reviewer_obj.username;
@@ -287,13 +317,13 b' ReviewersController = function () {'
287 // register IDS to check if we don't have this ID already in
317 // register IDS to check if we don't have this ID already in
288 var currentIds = [];
318 var currentIds = [];
289 var _els = self.$reviewMembers.find('li').toArray();
319 var _els = self.$reviewMembers.find('li').toArray();
290 for (el in _els){
320 for (el in _els) {
291 currentIds.push(_els[el].id)
321 currentIds.push(_els[el].id)
292 }
322 }
293
323
294 var userAllowedReview = function(userId) {
324 var userAllowedReview = function (userId) {
295 var allowed = true;
325 var allowed = true;
296 $.each(self.forbidReviewUsers, function(index, member_data) {
326 $.each(self.forbidReviewUsers, function (index, member_data) {
297 if (parseInt(userId) === member_data['user_id']) {
327 if (parseInt(userId) === member_data['user_id']) {
298 allowed = false;
328 allowed = false;
299 return false // breaks the loop
329 return false // breaks the loop
@@ -303,35 +333,38 b' ReviewersController = function () {'
303 };
333 };
304
334
305 var userAllowed = userAllowedReview(id);
335 var userAllowed = userAllowedReview(id);
306 if (!userAllowed){
336 if (!userAllowed) {
307 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
337 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
308 } else {
338 } else {
309 // only add if it's not there
339 // only add if it's not there
310 var alreadyReviewer = currentIds.indexOf('reviewer_'+id) != -1;
340 var alreadyReviewer = currentIds.indexOf('reviewer_' + id) != -1;
311
341
312 if (alreadyReviewer) {
342 if (alreadyReviewer) {
313 alert(_gettext('User `{0}` already in reviewers').format(username));
343 alert(_gettext('User `{0}` already in reviewers').format(username));
314 } else {
344 } else {
315 members.innerHTML += renderTemplate('reviewMemberEntry', {
345 members.innerHTML += renderTemplate('reviewMemberEntry', {
316 'member': reviewer_obj,
346 'member': reviewer_obj,
317 'mandatory': mandatory,
347 'mandatory': mandatory,
318 'allowed_to_update': true,
348 'allowed_to_update': true,
319 'review_status': 'not_reviewed',
349 'review_status': 'not_reviewed',
320 'review_status_label': _gettext('Not Reviewed'),
350 'review_status_label': _gettext('Not Reviewed'),
321 'reasons': reasons,
351 'reasons': reasons,
322 'create': true
352 'create': true
323 });
353 });
324 tooltipActivate();
354 tooltipActivate();
325 }
355 }
326 }
356 }
327
357
328 };
358 };
329
359
330 this.updateReviewers = function(repo_name, pull_request_id){
360 this.updateReviewers = function (repo_name, pull_request_id) {
331 var postData = $('#reviewers input').serialize();
361 var postData = $('#reviewers input').serialize();
332 _updatePullRequest(repo_name, pull_request_id, postData);
362 _updatePullRequest(repo_name, pull_request_id, postData);
333 };
363 };
334
364
365 this.handleDiffData = function (data) {
366 self.diffDataHandler(data)
367 }
335 };
368 };
336
369
337
370
@@ -393,6 +393,7 b' html[dir="rtl"] .select2-results {'
393 -moz-user-select: none;
393 -moz-user-select: none;
394 -ms-user-select: none;
394 -ms-user-select: none;
395 user-select: none;
395 user-select: none;
396 white-space: nowrap;
396 }
397 }
397
398
398 .select2-results-dept-1 .select2-result-label { padding-left: 20px }
399 .select2-results-dept-1 .select2-result-label { padding-left: 20px }
@@ -2,10 +2,8 b''
2 <%namespace name="base" file="/base/base.mako"/>
2 <%namespace name="base" file="/base/base.mako"/>
3
3
4 %if c.ancestor:
4 %if c.ancestor:
5 <div class="ancestor">${_('Common Ancestor Commit')}:
5 <div class="ancestor">${_('Compare was calculated based on this common ancestor commit')}:
6 <a href="${h.route_path('repo_commit', repo_name=c.repo_name, commit_id=c.ancestor)}">
6 <a href="${h.route_path('repo_commit', repo_name=c.repo_name, commit_id=c.ancestor)}">${h.short_id(c.ancestor)}</a>
7 ${h.short_id(c.ancestor)}
8 </a>. ${_('Compare was calculated based on this shared commit.')}
9 <input id="common_ancestor" type="hidden" name="common_ancestor" value="${c.ancestor}">
7 <input id="common_ancestor" type="hidden" name="common_ancestor" value="${c.ancestor}">
10 </div>
8 </div>
11 %endif
9 %endif
@@ -241,7 +241,93 b''
241 // custom code mirror
241 // custom code mirror
242 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
242 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
243
243
244 var diffDataHandler = function(data) {
245
246 $('#pull_request_overview').html(data);
247
248 var commitElements = data['commits'];
249 var files = data['files'];
250 var added = data['stats'][0]
251 var deleted = data['stats'][1]
252 var commonAncestorId = data['ancestor'];
253
254 var prTitleAndDesc = getTitleAndDescription(
255 sourceRef()[1], commitElements, 5);
256
257 var title = prTitleAndDesc[0];
258 var proposedDescription = prTitleAndDesc[1];
259
260 var useGeneratedTitle = (
261 $('#pullrequest_title').hasClass('autogenerated-title') ||
262 $('#pullrequest_title').val() === "");
263
264 if (title && useGeneratedTitle) {
265 // use generated title if we haven't specified our own
266 $('#pullrequest_title').val(title);
267 $('#pullrequest_title').addClass('autogenerated-title');
268
269 }
270
271 var useGeneratedDescription = (
272 !codeMirrorInstance._userDefinedValue ||
273 codeMirrorInstance.getValue() === "");
274
275 if (proposedDescription && useGeneratedDescription) {
276 // set proposed content, if we haven't defined our own,
277 // or we don't have description written
278 codeMirrorInstance._userDefinedValue = false; // reset state
279 codeMirrorInstance.setValue(proposedDescription);
280 }
281
282 // refresh our codeMirror so events kicks in and it's change aware
283 codeMirrorInstance.refresh();
284
285 var url_data = {
286 'repo_name': targetRepo(),
287 'target_repo': sourceRepo(),
288 'source_ref': targetRef()[2],
289 'source_ref_type': 'rev',
290 'target_ref': sourceRef()[2],
291 'target_ref_type': 'rev',
292 'merge': true,
293 '_': Date.now() // bypass browser caching
294 }; // gather the source/target ref and repo here
295 var url = pyroutes.url('repo_compare', url_data);
296
297 var msg = '<input id="common_ancestor" type="hidden" name="common_ancestor" value="{0}">'.format(commonAncestorId);
298 msg += '<input type="hidden" name="__start__" value="revisions:sequence">'
299
300 $.each(commitElements, function(idx, value) {
301 msg += '<input type="hidden" name="revisions" value="{0}">'.format(value["raw_id"]);
302 });
303
304 msg += '<input type="hidden" name="__end__" value="revisions:sequence">'
305 msg += _ngettext(
306 'This pull requests will consist of <strong>{0} commit</strong>.',
307 'This pull requests will consist of <strong>{0} commits</strong>.',
308 commitElements.length).format(commitElements.length)
309
310 msg += '\n';
311 msg += _ngettext(
312 '<strong>{0} file</strong> changed, ',
313 '<strong>{0} files</strong> changed, ',
314 files.length).format(files.length)
315 msg += '<span class="op-added">{0} lines inserted</span>, <span class="op-deleted">{1} lines deleted</span>.'.format(added, deleted)
316
317 msg += '\n\n <a class="" id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
318
319 if (commitElements.length) {
320 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
321 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
322 }
323 else {
324 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
325 }
326
327 };
328
244 reviewersController = new ReviewersController();
329 reviewersController = new ReviewersController();
330 reviewersController.diffDataHandler = diffDataHandler;
245
331
246 var queryTargetRepo = function(self, query) {
332 var queryTargetRepo = function(self, query) {
247 // cache ALL results if query is empty
333 // cache ALL results if query is empty
@@ -286,99 +372,6 b''
286 query.callback({results: data.results});
372 query.callback({results: data.results});
287 };
373 };
288
374
289 var loadRepoRefDiffPreview = function() {
290
291 var url_data = {
292 'repo_name': targetRepo(),
293 'target_repo': sourceRepo(),
294 'source_ref': targetRef()[2],
295 'source_ref_type': 'rev',
296 'target_ref': sourceRef()[2],
297 'target_ref_type': 'rev',
298 'merge': true,
299 '_': Date.now() // bypass browser caching
300 }; // gather the source/target ref and repo here
301
302 if (sourceRef().length !== 3 || targetRef().length !== 3) {
303 prButtonLock(true, "${_('Please select source and target')}");
304 return;
305 }
306 var url = pyroutes.url('repo_compare', url_data);
307
308 // lock PR button, so we cannot send PR before it's calculated
309 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
310
311 if (loadRepoRefDiffPreview._currentRequest) {
312 loadRepoRefDiffPreview._currentRequest.abort();
313 }
314
315 loadRepoRefDiffPreview._currentRequest = $.get(url)
316 .error(function(jqXHR, textStatus, errorThrown) {
317 if (textStatus !== 'abort') {
318 var prefix = "Error while processing request.\n"
319 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
320 ajaxErrorSwal(message);
321 }
322
323 })
324 .done(function(data) {
325 loadRepoRefDiffPreview._currentRequest = null;
326 $('#pull_request_overview').html(data);
327
328 var commitElements = $(data).find('tr[commit_id]');
329
330 var prTitleAndDesc = getTitleAndDescription(
331 sourceRef()[1], commitElements, 5);
332
333 var title = prTitleAndDesc[0];
334 var proposedDescription = prTitleAndDesc[1];
335
336 var useGeneratedTitle = (
337 $('#pullrequest_title').hasClass('autogenerated-title') ||
338 $('#pullrequest_title').val() === "");
339
340 if (title && useGeneratedTitle) {
341 // use generated title if we haven't specified our own
342 $('#pullrequest_title').val(title);
343 $('#pullrequest_title').addClass('autogenerated-title');
344
345 }
346
347 var useGeneratedDescription = (
348 !codeMirrorInstance._userDefinedValue ||
349 codeMirrorInstance.getValue() === "");
350
351 if (proposedDescription && useGeneratedDescription) {
352 // set proposed content, if we haven't defined our own,
353 // or we don't have description written
354 codeMirrorInstance._userDefinedValue = false; // reset state
355 codeMirrorInstance.setValue(proposedDescription);
356 }
357
358 // refresh our codeMirror so events kicks in and it's change aware
359 codeMirrorInstance.refresh();
360
361 var msg = '';
362 if (commitElements.length === 1) {
363 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
364 } else {
365 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
366 }
367
368 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
369
370 if (commitElements.length) {
371 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
372 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
373 }
374 else {
375 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
376 }
377
378
379 });
380 };
381
382 var Select2Box = function(element, overrides) {
375 var Select2Box = function(element, overrides) {
383 var globalDefaults = {
376 var globalDefaults = {
384 dropdownAutoWidth: true,
377 dropdownAutoWidth: true,
@@ -476,13 +469,11 b''
476 targetRepoSelect2.initRepo(defaultTargetRepo, false);
469 targetRepoSelect2.initRepo(defaultTargetRepo, false);
477
470
478 $sourceRef.on('change', function(e){
471 $sourceRef.on('change', function(e){
479 loadRepoRefDiffPreview();
480 reviewersController.loadDefaultReviewers(
472 reviewersController.loadDefaultReviewers(
481 sourceRepo(), sourceRef(), targetRepo(), targetRef());
473 sourceRepo(), sourceRef(), targetRepo(), targetRef());
482 });
474 });
483
475
484 $targetRef.on('change', function(e){
476 $targetRef.on('change', function(e){
485 loadRepoRefDiffPreview();
486 reviewersController.loadDefaultReviewers(
477 reviewersController.loadDefaultReviewers(
487 sourceRepo(), sourceRef(), targetRepo(), targetRef());
478 sourceRepo(), sourceRef(), targetRepo(), targetRef());
488 });
479 });
@@ -502,7 +493,6 b''
502 success: function(data) {
493 success: function(data) {
503 $('#target_ref_loading').hide();
494 $('#target_ref_loading').hide();
504 targetRepoChanged(data);
495 targetRepoChanged(data);
505 loadRepoRefDiffPreview();
506 },
496 },
507 error: function(jqXHR, textStatus, errorThrown) {
497 error: function(jqXHR, textStatus, errorThrown) {
508 var prefix = "Error while fetching entries.\n"
498 var prefix = "Error while fetching entries.\n"
@@ -533,8 +523,8 b''
533 % if c.default_source_ref:
523 % if c.default_source_ref:
534 // in case we have a pre-selected value, use it now
524 // in case we have a pre-selected value, use it now
535 $sourceRef.select2('val', '${c.default_source_ref}');
525 $sourceRef.select2('val', '${c.default_source_ref}');
536 // diff preview load
526
537 loadRepoRefDiffPreview();
527
538 // default reviewers
528 // default reviewers
539 reviewersController.loadDefaultReviewers(
529 reviewersController.loadDefaultReviewers(
540 sourceRepo(), sourceRef(), targetRepo(), targetRef());
530 sourceRepo(), sourceRef(), targetRepo(), targetRef());
General Comments 0
You need to be logged in to leave comments. Login now