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__ = 10 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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] |
|
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': |
|
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 ( |
|
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, |
|
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_ |
|
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 |
|
|
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. |
|
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,11 +99,16 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 | |||
@@ -122,6 +126,8 b' ReviewersController = function () {' | |||||
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 | |||
@@ -206,8 +212,7 b' ReviewersController = function () {' | |||||
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(); | |
@@ -233,22 +241,43 b' ReviewersController = function () {' | |||||
233 |
|
|
241 | 'target_ref': targetRef[2] | |
234 |
|
|
242 | }); | |
235 |
|
243 | |||
236 |
self.currentRequest = $. |
|
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 | |
|
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 | // check those, refactor |
|
283 | // check those, refactor | |
@@ -268,14 +297,15 b' ReviewersController = function () {' | |||||
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 | }; | |
|
304 | ||||
276 | this.reviewMemberEntry = function() { |
|
305 | this.reviewMemberEntry = function () { | |
277 |
|
306 | |||
278 | }; |
|
307 | }; | |
|
308 | ||||
279 | this.addReviewMember = function(reviewer_obj, reasons, mandatory) { |
|
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; | |
@@ -332,6 +362,9 b' ReviewersController = function () {' | |||||
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">${_('Com |
|
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