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 | 48 | EXTENSIONS = {} |
|
49 | 49 | |
|
50 | 50 | __version__ = ('.'.join((str(each) for each in VERSION[:3]))) |
|
51 |
__dbversion__ = 10 |
|
|
51 | __dbversion__ = 107 # defines current db version for migrations | |
|
52 | 52 | __platform__ = platform.system() |
|
53 | 53 | __license__ = 'AGPLv3, and Commercial License' |
|
54 | 54 | __author__ = 'RhodeCode GmbH' |
@@ -686,28 +686,9 b' def create_pull_request(' | |||
|
686 | 686 | full_source_ref = resolve_ref_or_error(source_ref, source_db_repo) |
|
687 | 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 | 689 | source_commit = get_commit_or_error(full_source_ref, source_db_repo) |
|
693 | 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 | 692 | reviewer_objects = Optional.extract(reviewers) or [] |
|
712 | 693 | |
|
713 | 694 | # serialize and validate passed in given reviewers |
@@ -727,16 +708,16 b' def create_pull_request(' | |||
|
727 | 708 | PullRequestModel().get_reviewer_functions() |
|
728 | 709 | |
|
729 | 710 | # recalculate reviewers logic, to make sure we can validate this |
|
730 |
reviewer |
|
|
711 | default_reviewers_data = get_default_reviewers_data( | |
|
731 | 712 | owner, source_db_repo, |
|
732 | 713 | source_commit, target_db_repo, target_commit) |
|
733 | 714 | |
|
734 | 715 | # now MERGE our given with the calculated |
|
735 |
reviewer_objects = reviewer |
|
|
716 | reviewer_objects = default_reviewers_data['reviewers'] + reviewer_objects | |
|
736 | 717 | |
|
737 | 718 | try: |
|
738 | 719 | reviewers = validate_default_reviewers( |
|
739 |
reviewer_objects, reviewer |
|
|
720 | reviewer_objects, default_reviewers_data) | |
|
740 | 721 | except ValueError as e: |
|
741 | 722 | raise JSONRPCError('Reviewers Validation: {}'.format(e)) |
|
742 | 723 | |
@@ -748,6 +729,24 b' def create_pull_request(' | |||
|
748 | 729 | source_ref=title_source_ref, |
|
749 | 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 | 750 | # fetch renderer, if set fallback to plain in case of PR |
|
752 | 751 | rc_config = SettingsModel().get_all_settings() |
|
753 | 752 | default_system_renderer = rc_config.get('rhodecode_markup_renderer', 'plain') |
@@ -760,12 +759,13 b' def create_pull_request(' | |||
|
760 | 759 | source_ref=full_source_ref, |
|
761 | 760 | target_repo=target_repo, |
|
762 | 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 | 764 | reviewers=reviewers, |
|
765 | 765 | title=title, |
|
766 | 766 | description=description, |
|
767 | 767 | description_renderer=description_renderer, |
|
768 |
reviewer_data=reviewer |
|
|
768 | reviewer_data=default_reviewers_data, | |
|
769 | 769 | auth_user=apiuser |
|
770 | 770 | ) |
|
771 | 771 |
@@ -484,7 +484,7 b' class TestCompareView(object):' | |||
|
484 | 484 | |
|
485 | 485 | # outgoing commits between those commits |
|
486 | 486 | compare_page = ComparePage(response) |
|
487 |
compare_page.contains_commits(commits=[commit1] |
|
|
487 | compare_page.contains_commits(commits=[commit1]) | |
|
488 | 488 | |
|
489 | 489 | def test_errors_when_comparing_unknown_source_repo(self, backend): |
|
490 | 490 | repo = backend.repo |
@@ -641,6 +641,7 b' class ComparePage(AssertResponse):' | |||
|
641 | 641 | self.contains_one_link( |
|
642 | 642 | 'r%s:%s' % (commit.idx, commit.short_id), |
|
643 | 643 | self._commit_url(commit)) |
|
644 | ||
|
644 | 645 | if ancestors: |
|
645 | 646 | response.mustcontain('Ancestor') |
|
646 | 647 | for ancestor in ancestors: |
@@ -20,6 +20,9 b'' | |||
|
20 | 20 | |
|
21 | 21 | from rhodecode.lib import helpers as h |
|
22 | 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 | 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 | 51 | def get_default_reviewers_data( |
|
49 | 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 | 60 | reasons = ['Default reviewer', 'Repository owner'] |
|
54 | 61 | json_reviewers = [reviewer_as_json( |
|
55 | 62 | user=target_repo.user, reasons=reasons, mandatory=False, rules=None)] |
|
56 | 63 | |
|
57 | 64 | return { |
|
58 |
'api_ver': |
|
|
65 | 'api_ver': REVIEWER_API_VERSION, # define version for later possible schema upgrade | |
|
66 | 'diff_info': diff_info, | |
|
59 | 67 | 'reviewers': json_reviewers, |
|
60 | 68 | 'rules': {}, |
|
61 | 69 | 'rules_data': {}, |
@@ -40,12 +40,12 b' from rhodecode.lib.auth import (' | |||
|
40 | 40 | NotAnonymous, CSRFRequired) |
|
41 | 41 | from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode |
|
42 | 42 | from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason |
|
43 |
from rhodecode.lib.vcs.exceptions import ( |
|
|
44 | RepositoryRequirementError, EmptyRepositoryError) | |
|
43 | from rhodecode.lib.vcs.exceptions import ( | |
|
44 | CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError) | |
|
45 | 45 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
46 | 46 | from rhodecode.model.comment import CommentsModel |
|
47 | from rhodecode.model.db import (func, or_, PullRequest, PullRequestVersion, | |
|
48 | ChangesetComment, ChangesetStatus, Repository) | |
|
47 | from rhodecode.model.db import ( | |
|
48 | func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository) | |
|
49 | 49 | from rhodecode.model.forms import PullRequestForm |
|
50 | 50 | from rhodecode.model.meta import Session |
|
51 | 51 | from rhodecode.model.pull_request import PullRequestModel, MergeCheck |
@@ -210,10 +210,12 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
210 | 210 | return caching_enabled |
|
211 | 211 | |
|
212 | 212 | def _get_diffset(self, source_repo_name, source_repo, |
|
213 | ancestor_commit, | |
|
213 | 214 | source_ref_id, target_ref_id, |
|
214 | 215 | target_commit, source_commit, diff_limit, file_limit, |
|
215 | 216 | fulldiff, hide_whitespace_changes, diff_context): |
|
216 | 217 | |
|
218 | target_ref_id = ancestor_commit.raw_id | |
|
217 | 219 | vcs_diff = PullRequestModel().get_diff( |
|
218 | 220 | source_repo, source_ref_id, target_ref_id, |
|
219 | 221 | hide_whitespace_changes, diff_context) |
@@ -278,6 +280,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
278 | 280 | _new_state = { |
|
279 | 281 | 'created': PullRequest.STATE_CREATED, |
|
280 | 282 | }.get(self.request.GET.get('force_state')) |
|
283 | ||
|
281 | 284 | if c.is_super_admin and _new_state: |
|
282 | 285 | with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state): |
|
283 | 286 | h.flash( |
@@ -557,7 +560,8 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
557 | 560 | source_scm, |
|
558 | 561 | target_commit, |
|
559 | 562 | target_ref_id, |
|
560 |
target_scm, |
|
|
563 | target_scm, | |
|
564 | maybe_unreachable=maybe_unreachable) | |
|
561 | 565 | |
|
562 | 566 | # register our commit range |
|
563 | 567 | for comm in commit_cache.values(): |
@@ -591,11 +595,10 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
591 | 595 | has_proper_diff_cache = cached_diff and cached_diff.get('commits') |
|
592 | 596 | if not force_recache and has_proper_diff_cache: |
|
593 | 597 | c.diffset = cached_diff['diff'] |
|
594 | (ancestor_commit, commit_cache, missing_requirements, | |
|
595 | source_commit, target_commit) = cached_diff['commits'] | |
|
596 | 598 | else: |
|
597 | 599 | c.diffset = self._get_diffset( |
|
598 | 600 | c.source_repo.repo_name, commits_source_repo, |
|
601 | c.ancestor_commit, | |
|
599 | 602 | source_ref_id, target_ref_id, |
|
600 | 603 | target_commit, source_commit, |
|
601 | 604 | diff_limit, file_limit, c.fulldiff, |
@@ -675,8 +678,10 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
675 | 678 | |
|
676 | 679 | # calculate the diff for commits between versions |
|
677 | 680 | c.commit_changes = [] |
|
678 | mark = lambda cs, fw: list( | |
|
679 | h.itertools.izip_longest([], cs, fillvalue=fw)) | |
|
681 | ||
|
682 | def mark(cs, fw): | |
|
683 | return list(h.itertools.izip_longest([], cs, fillvalue=fw)) | |
|
684 | ||
|
680 | 685 | for c_type, raw_id in mark(commit_changes.added, 'a') \ |
|
681 | 686 | + mark(commit_changes.removed, 'r') \ |
|
682 | 687 | + mark(commit_changes.common, 'c'): |
@@ -739,13 +744,14 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
739 | 744 | except RepositoryRequirementError: |
|
740 | 745 | log.warning('Failed to get all required data from repo', exc_info=True) |
|
741 | 746 | missing_requirements = True |
|
742 | ancestor_commit = None | |
|
747 | ||
|
748 | pr_ancestor_id = pull_request_at_ver.common_ancestor_id | |
|
749 | ||
|
743 | 750 | try: |
|
744 |
ancestor_ |
|
|
745 | source_commit.raw_id, target_commit.raw_id, target_scm) | |
|
746 | ancestor_commit = source_scm.get_commit(ancestor_id) | |
|
751 | ancestor_commit = source_scm.get_commit(pr_ancestor_id) | |
|
747 | 752 | except Exception: |
|
748 | 753 | ancestor_commit = None |
|
754 | ||
|
749 | 755 | return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit |
|
750 | 756 | |
|
751 | 757 | def assure_not_empty_repo(self): |
@@ -949,6 +955,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
949 | 955 | target_repo = _form['target_repo'] |
|
950 | 956 | target_ref = _form['target_ref'] |
|
951 | 957 | commit_ids = _form['revisions'][::-1] |
|
958 | common_ancestor_id = _form['common_ancestor'] | |
|
952 | 959 | |
|
953 | 960 | # find the ancestor for this pr |
|
954 | 961 | source_db_repo = Repository.get_by_repo_name(_form['source_repo']) |
@@ -1035,6 +1042,7 b' class RepoPullRequestsView(RepoAppView, ' | |||
|
1035 | 1042 | target_repo=target_repo, |
|
1036 | 1043 | target_ref=target_ref, |
|
1037 | 1044 | revisions=commit_ids, |
|
1045 | common_ancestor_id=common_ancestor_id, | |
|
1038 | 1046 | reviewers=reviewers, |
|
1039 | 1047 | title=pullrequest_title, |
|
1040 | 1048 | description=description, |
@@ -54,8 +54,20 b' class RepoReviewRulesView(RepoAppView):' | |||
|
54 | 54 | renderer='json_ext') |
|
55 | 55 | def repo_default_reviewers_data(self): |
|
56 | 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 | 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 | 70 | review_data = get_default_reviewers_data( |
|
60 |
|
|
|
71 | current_user, source_repo, source_commit, target_repo, target_commit) | |
|
72 | ||
|
61 | 73 | return review_data |
@@ -580,6 +580,9 b' class GitRepository(BaseRepository):' | |||
|
580 | 580 | return len(self.commit_ids) |
|
581 | 581 | |
|
582 | 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 | 586 | if commit_id1 == commit_id2: |
|
584 | 587 | return commit_id1 |
|
585 | 588 | |
@@ -600,6 +603,8 b' class GitRepository(BaseRepository):' | |||
|
600 | 603 | ['merge-base', commit_id1, commit_id2]) |
|
601 | 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 | 608 | return ancestor_id |
|
604 | 609 | |
|
605 | 610 | def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None): |
@@ -297,13 +297,20 b' class MercurialRepository(BaseRepository' | |||
|
297 | 297 | return update_cache |
|
298 | 298 | |
|
299 | 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 | 303 | if commit_id1 == commit_id2: |
|
301 | 304 | return commit_id1 |
|
302 | 305 | |
|
303 | 306 | ancestors = self._remote.revs_from_revspec( |
|
304 | 307 | "ancestor(id(%s), id(%s))", commit_id1, commit_id2, |
|
305 | 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 | 315 | def compare(self, commit_id1, commit_id2, repo2, merge, pre_load=None): |
|
309 | 316 | if commit_id1 == commit_id2: |
@@ -3989,6 +3989,8 b' class _PullRequestBase(BaseModel):' | |||
|
3989 | 3989 | _revisions = Column( |
|
3990 | 3990 | 'revisions', UnicodeText().with_variant(UnicodeText(20500), 'mysql')) |
|
3991 | 3991 | |
|
3992 | common_ancestor_id = Column('common_ancestor_id', Unicode(255), nullable=True) | |
|
3993 | ||
|
3992 | 3994 | @declared_attr |
|
3993 | 3995 | def source_repo_id(cls): |
|
3994 | 3996 | # TODO: dan: rename column to source_repo_id |
@@ -4302,7 +4304,7 b' class PullRequest(Base, _PullRequestBase' | |||
|
4302 | 4304 | attrs.source_ref_parts = pull_request_obj.source_ref_parts |
|
4303 | 4305 | attrs.target_ref_parts = pull_request_obj.target_ref_parts |
|
4304 | 4306 | attrs.revisions = pull_request_obj.revisions |
|
4305 | ||
|
4307 | attrs.common_ancestor_id = pull_request_obj.common_ancestor_id | |
|
4306 | 4308 | attrs.shadow_merge_ref = org_pull_request_obj.shadow_merge_ref |
|
4307 | 4309 | attrs.reviewer_data = org_pull_request_obj.reviewer_data |
|
4308 | 4310 | attrs.reviewer_data_json = org_pull_request_obj.reviewer_data_json |
@@ -35,6 +35,7 b' import collections' | |||
|
35 | 35 | from pyramid import compat |
|
36 | 36 | from pyramid.threadlocal import get_current_request |
|
37 | 37 | |
|
38 | from rhodecode.lib.vcs.nodes import FileNode | |
|
38 | 39 | from rhodecode.translation import lazy_ugettext |
|
39 | 40 | from rhodecode.lib import helpers as h, hooks_utils, diffs |
|
40 | 41 | from rhodecode.lib import audit_logger |
@@ -82,6 +83,126 b' class UpdateResponse(object):' | |||
|
82 | 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 | 206 | class PullRequestModel(BaseModel): |
|
86 | 207 | |
|
87 | 208 | cls = PullRequest |
@@ -453,6 +574,7 b' class PullRequestModel(BaseModel):' | |||
|
453 | 574 | |
|
454 | 575 | def create(self, created_by, source_repo, source_ref, target_repo, |
|
455 | 576 | target_ref, revisions, reviewers, title, description=None, |
|
577 | common_ancestor_id=None, | |
|
456 | 578 | description_renderer=None, |
|
457 | 579 | reviewer_data=None, translator=None, auth_user=None): |
|
458 | 580 | translator = translator or get_current_request().translate |
@@ -474,6 +596,8 b' class PullRequestModel(BaseModel):' | |||
|
474 | 596 | pull_request.author = created_by_user |
|
475 | 597 | pull_request.reviewer_data = reviewer_data |
|
476 | 598 | pull_request.pull_request_state = pull_request.STATE_CREATING |
|
599 | pull_request.common_ancestor_id = common_ancestor_id | |
|
600 | ||
|
477 | 601 | Session().add(pull_request) |
|
478 | 602 | Session().flush() |
|
479 | 603 | |
@@ -805,8 +929,17 b' class PullRequestModel(BaseModel):' | |||
|
805 | 929 | target_commit.raw_id, source_commit.raw_id, source_repo, merge=True, |
|
806 | 930 | pre_load=pre_load) |
|
807 | 931 | |
|
808 | ancestor_commit_id = source_repo.get_common_ancestor( | |
|
809 | source_commit.raw_id, target_commit.raw_id, target_repo) | |
|
932 | target_ref = target_commit.raw_id | |
|
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 | 944 | pull_request.source_ref = '%s:%s:%s' % ( |
|
812 | 945 | source_ref_type, source_ref_name, source_commit.raw_id) |
@@ -913,6 +1046,7 b' class PullRequestModel(BaseModel):' | |||
|
913 | 1046 | version.reviewer_data = pull_request.reviewer_data |
|
914 | 1047 | |
|
915 | 1048 | version.revisions = pull_request.revisions |
|
1049 | version.common_ancestor_id = pull_request.common_ancestor_id | |
|
916 | 1050 | version.pull_request = pull_request |
|
917 | 1051 | Session().add(version) |
|
918 | 1052 | Session().flush() |
@@ -467,9 +467,9 b' ul.auth_plugins {' | |||
|
467 | 467 | #pr_open_message { |
|
468 | 468 | border: @border-thickness solid #fff; |
|
469 | 469 | border-radius: @border-radius; |
|
470 | padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0; | |
|
471 | 470 | text-align: left; |
|
472 | 471 | overflow: hidden; |
|
472 | white-space: pre-line; | |
|
473 | 473 | } |
|
474 | 474 | |
|
475 | 475 | .pr-details-title { |
@@ -1796,7 +1796,7 b' table.integrations {' | |||
|
1796 | 1796 | } |
|
1797 | 1797 | |
|
1798 | 1798 | div.ancestor { |
|
1799 | line-height: 33px; | |
|
1799 | ||
|
1800 | 1800 | } |
|
1801 | 1801 | |
|
1802 | 1802 | .cs_icon_td input[type="checkbox"] { |
@@ -205,7 +205,9 b' select.select2{height:28px;visibility:hi' | |||
|
205 | 205 | font-family: @text-regular; |
|
206 | 206 | color: @grey2; |
|
207 | 207 | cursor: pointer; |
|
208 | white-space: nowrap; | |
|
208 | 209 | } |
|
210 | ||
|
209 | 211 | &.select2-result-with-children { |
|
210 | 212 | |
|
211 | 213 | .select2-result-label { |
@@ -220,6 +222,7 b' select.select2{height:28px;visibility:hi' | |||
|
220 | 222 | font-family: @text-regular; |
|
221 | 223 | color: @grey2; |
|
222 | 224 | cursor: pointer; |
|
225 | white-space: nowrap; | |
|
223 | 226 | } |
|
224 | 227 | } |
|
225 | 228 | } |
@@ -22,7 +22,7 b' var _gettext = function (s) {' | |||
|
22 | 22 | if (_TM.hasOwnProperty(s)) { |
|
23 | 23 | return _TM[s]; |
|
24 | 24 | } |
|
25 |
i18nLog. |
|
|
25 | i18nLog.warning( | |
|
26 | 26 | 'String `' + s + '` was requested but cannot be ' + |
|
27 | 27 | 'found in translation table'); |
|
28 | 28 | return s |
@@ -34,3 +34,5 b' var _ngettext = function (singular, plur' | |||
|
34 | 34 | } |
|
35 | 35 | return _gettext(plural) |
|
36 | 36 | }; |
|
37 | ||
|
38 |
@@ -75,12 +75,12 b' var getTitleAndDescription = function(so' | |||
|
75 | 75 | var desc = ''; |
|
76 | 76 | |
|
77 | 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 | 79 | desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n'; |
|
80 | 80 | }); |
|
81 | 81 | // only 1 commit, use commit message as title |
|
82 | 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 | 84 | title = rawMessage.split('\n')[0]; |
|
85 | 85 | } |
|
86 | 86 | else { |
@@ -92,7 +92,6 b' var getTitleAndDescription = function(so' | |||
|
92 | 92 | }; |
|
93 | 93 | |
|
94 | 94 | |
|
95 | ||
|
96 | 95 | ReviewersController = function () { |
|
97 | 96 | var self = this; |
|
98 | 97 | this.$reviewRulesContainer = $('#review_rules'); |
@@ -100,11 +99,16 b' ReviewersController = function () {' | |||
|
100 | 99 | this.forbidReviewUsers = undefined; |
|
101 | 100 | this.$reviewMembers = $('#review_members'); |
|
102 | 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 | 106 | this.defaultForbidReviewUsers = function() { |
|
105 | 107 | return [ |
|
106 | {'username': 'default', | |
|
107 | 'user_id': templateContext.default_user.user_id} | |
|
108 | { | |
|
109 | 'username': 'default', | |
|
110 | 'user_id': templateContext.default_user.user_id | |
|
111 | } | |
|
108 | 112 | ]; |
|
109 | 113 | }; |
|
110 | 114 | |
@@ -122,6 +126,8 b' ReviewersController = function () {' | |||
|
122 | 126 | }; |
|
123 | 127 | |
|
124 | 128 | this.loadReviewRules = function(data) { |
|
129 | self.diffData = data; | |
|
130 | ||
|
125 | 131 | // reset forbidden Users |
|
126 | 132 | this.forbidReviewUsers = self.defaultForbidReviewUsers(); |
|
127 | 133 | |
@@ -206,8 +212,7 b' ReviewersController = function () {' | |||
|
206 | 212 | this.loadDefaultReviewers = function(sourceRepo, sourceRef, targetRepo, targetRef) { |
|
207 | 213 | |
|
208 | 214 | if (self.currentRequest) { |
|
209 | // make sure we cleanup old running requests before triggering this | |
|
210 | // again | |
|
215 | // make sure we cleanup old running requests before triggering this again | |
|
211 | 216 | self.currentRequest.abort(); |
|
212 | 217 | } |
|
213 | 218 | |
@@ -218,6 +223,9 b' ReviewersController = function () {' | |||
|
218 | 223 | prButtonLock(true, null, 'reviewers'); |
|
219 | 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 | 229 | if (sourceRef.length !== 3 || targetRef.length !== 3) { |
|
222 | 230 | // don't load defaults in case we're missing some refs... |
|
223 | 231 | $('.calculate-reviewers').hide(); |
@@ -233,22 +241,43 b' ReviewersController = function () {' | |||
|
233 | 241 |
|
|
234 | 242 |
|
|
235 | 243 | |
|
236 |
self.currentRequest = $. |
|
|
237 | .done(function(data) { | |
|
244 | self.currentRequest = $.ajax({ | |
|
245 | url: url, | |
|
246 | headers: {'X-PARTIAL-XHR': true}, | |
|
247 | type: 'GET', | |
|
248 | success: function (data) { | |
|
249 | ||
|
238 | 250 | self.currentRequest = null; |
|
239 | 251 | |
|
240 | 252 | // review rules |
|
241 | 253 | self.loadReviewRules(data); |
|
254 | self.handleDiffData(data["diff_info"]); | |
|
242 | 255 | |
|
243 | 256 | for (var i = 0; i < data.reviewers.length; i++) { |
|
244 | 257 | var reviewer = data.reviewers[i]; |
|
245 | self.addReviewMember( | |
|
246 | reviewer, reviewer.reasons, reviewer.mandatory); | |
|
258 | self.addReviewMember(reviewer, reviewer.reasons, reviewer.mandatory); | |
|
247 | 259 | } |
|
248 | 260 | $('.calculate-reviewers').hide(); |
|
249 | 261 | prButtonLock(false, null, 'reviewers'); |
|
250 | 262 | $('#user').show(); // show user autocomplete after load |
|
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 | } | |
|
251 | 279 |
|
|
280 | ||
|
252 | 281 | }; |
|
253 | 282 | |
|
254 | 283 | // check those, refactor |
@@ -268,14 +297,15 b' ReviewersController = function () {' | |||
|
268 | 297 | obj.addClass('to-delete'); |
|
269 | 298 | obj.css({"text-decoration":"line-through", "opacity": 0.5}); |
|
270 | 299 | } |
|
271 | } | |
|
272 | else{ | |
|
300 | } else { | |
|
273 | 301 | $('#reviewer_{0}'.format(reviewer_id)).remove(); |
|
274 | 302 | } |
|
275 | 303 | }; |
|
304 | ||
|
276 | 305 | this.reviewMemberEntry = function() { |
|
277 | 306 | |
|
278 | 307 | }; |
|
308 | ||
|
279 | 309 | this.addReviewMember = function(reviewer_obj, reasons, mandatory) { |
|
280 | 310 | var members = self.$reviewMembers.get(0); |
|
281 | 311 | var id = reviewer_obj.user_id; |
@@ -332,6 +362,9 b' ReviewersController = function () {' | |||
|
332 | 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 | 393 | -moz-user-select: none; |
|
394 | 394 | -ms-user-select: none; |
|
395 | 395 | user-select: none; |
|
396 | white-space: nowrap; | |
|
396 | 397 | } |
|
397 | 398 | |
|
398 | 399 | .select2-results-dept-1 .select2-result-label { padding-left: 20px } |
@@ -2,10 +2,8 b'' | |||
|
2 | 2 | <%namespace name="base" file="/base/base.mako"/> |
|
3 | 3 | |
|
4 | 4 | %if c.ancestor: |
|
5 |
<div class="ancestor">${_('Com |
|
|
6 | <a href="${h.route_path('repo_commit', repo_name=c.repo_name, commit_id=c.ancestor)}"> | |
|
7 | ${h.short_id(c.ancestor)} | |
|
8 | </a>. ${_('Compare was calculated based on this shared 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)}">${h.short_id(c.ancestor)}</a> | |
|
9 | 7 | <input id="common_ancestor" type="hidden" name="common_ancestor" value="${c.ancestor}"> |
|
10 | 8 | </div> |
|
11 | 9 | %endif |
@@ -241,7 +241,93 b'' | |||
|
241 | 241 | // custom code mirror |
|
242 | 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 | 329 | reviewersController = new ReviewersController(); |
|
330 | reviewersController.diffDataHandler = diffDataHandler; | |
|
245 | 331 | |
|
246 | 332 | var queryTargetRepo = function(self, query) { |
|
247 | 333 | // cache ALL results if query is empty |
@@ -286,99 +372,6 b'' | |||
|
286 | 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 | 375 | var Select2Box = function(element, overrides) { |
|
383 | 376 | var globalDefaults = { |
|
384 | 377 | dropdownAutoWidth: true, |
@@ -476,13 +469,11 b'' | |||
|
476 | 469 | targetRepoSelect2.initRepo(defaultTargetRepo, false); |
|
477 | 470 | |
|
478 | 471 | $sourceRef.on('change', function(e){ |
|
479 | loadRepoRefDiffPreview(); | |
|
480 | 472 | reviewersController.loadDefaultReviewers( |
|
481 | 473 | sourceRepo(), sourceRef(), targetRepo(), targetRef()); |
|
482 | 474 | }); |
|
483 | 475 | |
|
484 | 476 | $targetRef.on('change', function(e){ |
|
485 | loadRepoRefDiffPreview(); | |
|
486 | 477 | reviewersController.loadDefaultReviewers( |
|
487 | 478 | sourceRepo(), sourceRef(), targetRepo(), targetRef()); |
|
488 | 479 | }); |
@@ -502,7 +493,6 b'' | |||
|
502 | 493 | success: function(data) { |
|
503 | 494 | $('#target_ref_loading').hide(); |
|
504 | 495 | targetRepoChanged(data); |
|
505 | loadRepoRefDiffPreview(); | |
|
506 | 496 | }, |
|
507 | 497 | error: function(jqXHR, textStatus, errorThrown) { |
|
508 | 498 | var prefix = "Error while fetching entries.\n" |
@@ -533,8 +523,8 b'' | |||
|
533 | 523 | % if c.default_source_ref: |
|
534 | 524 | // in case we have a pre-selected value, use it now |
|
535 | 525 | $sourceRef.select2('val', '${c.default_source_ref}'); |
|
536 | // diff preview load | |
|
537 | loadRepoRefDiffPreview(); | |
|
526 | ||
|
527 | ||
|
538 | 528 | // default reviewers |
|
539 | 529 | reviewersController.loadDefaultReviewers( |
|
540 | 530 | sourceRepo(), sourceRef(), targetRepo(), targetRef()); |
General Comments 0
You need to be logged in to leave comments.
Login now