Changeset - 3dbb625d5f9c
[Not reviewed]
default
0 13 0
Mads Kiilerich (mads) - 8 years ago 2018-02-12 02:38:02
mads@kiilerich.com
Grafted from: c1860c537e45
vcs: introduce 'branches' attribute on changesets, making it possible for Git to show multiple branches for a changeset

Mercurial changesets will always have have exactly one branch (which might be
"default"). The VCS data model was the same.

Git allows for a changeset to have 0 or more branches ... and possibly one of
them as active. The right data model is thus to have an enumerable of branches.

We thus add a 'branches' attribute and use it where applicable.

The existing 'branch' attribute used some heuristics to decide which branch use
as "the" branch ... and in some places code (and tests) rely on that. We thus
keep that old method, knowing that some of its uses probably should move to
'branches'.

The code for retrieving Git branches is based on work by Dominik Ruf.
13 files changed with 48 insertions and 21 deletions:
0 comments (0 inline, 0 general)
kallithea/controllers/feed.py
Show inline comments
 
@@ -54,26 +54,26 @@ class FeedController(BaseRepoController)
 
    @LoginRequired(api_access=True, allow_default_user=True)
 
    @HasRepoPermissionLevelDecorator('read')
 
    def _before(self, *args, **kwargs):
 
        super(FeedController, self)._before(*args, **kwargs)
 

	
 
    def _get_title(self, cs):
 
        return h.shorter(cs.message, 160)
 

	
 
    def __get_desc(self, cs):
 
        desc_msg = [(_('%s committed on %s')
 
                     % (h.person(cs.author), h.fmt_date(cs.date))) + '<br/>']
 
        # branches, tags, bookmarks
 
        if cs.branch:
 
            desc_msg.append('branch: %s<br/>' % cs.branch)
 
        for branch in cs.branches:
 
            desc_msg.append('branch: %s<br/>' % branch)
 
        for book in cs.bookmarks:
 
            desc_msg.append('bookmark: %s<br/>' % book)
 
        for tag in cs.tags:
 
            desc_msg.append('tag: %s<br/>' % tag)
 

	
 
        changes = []
 
        diff_limit = safe_int(CONFIG.get('rss_cut_off_limit', 32 * 1024))
 
        raw_diff = cs.diff()
 
        diff_processor = DiffProcessor(raw_diff,
 
                                       diff_limit=diff_limit,
 
                                       inline_diff=False)
 

	
kallithea/controllers/files.py
Show inline comments
 
@@ -180,25 +180,25 @@ class FilesController(BaseRepoController
 
                    c.authors.append((h.email(a), h.person(a)))
 
            else:
 
                c.authors = c.file_history = []
 
        except RepositoryError as e:
 
            h.flash(safe_str(e), category='error')
 
            raise HTTPNotFound()
 

	
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            return render('files/files_ypjax.html')
 

	
 
        # TODO: tags and bookmarks?
 
        c.revision_options = [(c.changeset.raw_id,
 
                              _('%s at %s') % (c.changeset.branch, h.short_id(c.changeset.raw_id)))] + \
 
                              _('%s at %s') % (b, h.short_id(c.changeset.raw_id))) for b in c.changeset.branches] + \
 
            [(n, b) for b, n in c.db_repo_scm_instance.branches.items()]
 
        if c.db_repo_scm_instance.closed_branches:
 
            prefix = _('(closed)') + ' '
 
            c.revision_options += [('-', '-')] + \
 
                [(n, prefix + b) for b, n in c.db_repo_scm_instance.closed_branches.items()]
 

	
 
        return render('files/files.html')
 

	
 
    @LoginRequired(allow_default_user=True)
 
    @HasRepoPermissionLevelDecorator('read')
 
    @jsonify
 
    def history(self, repo_name, revision, f_path):
 
@@ -746,25 +746,25 @@ class FilesController(BaseRepoController
 
        if changesets is None:
 
            try:
 
                changesets = tip_cs.get_file_history(f_path)
 
            except (NodeDoesNotExistError, ChangesetError):
 
                # this node is not present at tip !
 
                changesets = cs.get_file_history(f_path)
 
        hist_l = []
 

	
 
        changesets_group = ([], _("Changesets"))
 
        branches_group = ([], _("Branches"))
 
        tags_group = ([], _("Tags"))
 
        for chs in changesets:
 
            #_branch = '(%s)' % chs.branch if (cs.repository.alias == 'hg') else ''
 
            # TODO: loop over chs.branches ... but that will not give all the bogus None branches for Git ...
 
            _branch = chs.branch
 
            n_desc = '%s (%s)' % (h.show_id(chs), _branch)
 
            changesets_group[0].append((chs.raw_id, n_desc,))
 
        hist_l.append(changesets_group)
 

	
 
        for name, chs in c.db_repo_scm_instance.branches.items():
 
            branches_group[0].append((chs, name),)
 
        hist_l.append(branches_group)
 

	
 
        for name, chs in c.db_repo_scm_instance.tags.items():
 
            tags_group[0].append((chs, name),)
 
        hist_l.append(tags_group)
kallithea/controllers/pullrequests.py
Show inline comments
 
@@ -93,29 +93,29 @@ class PullrequestsController(BaseRepoCon
 
            branch = safe_str(branch)
 

	
 
        if branch_rev:
 
            branch_rev = safe_str(branch_rev)
 
            # a revset not restricting to merge() would be better
 
            # (especially because it would get the branch point)
 
            # ... but is currently too expensive
 
            # including branches of children could be nice too
 
            peerbranches = set()
 
            for i in repo._repo.revs(
 
                "sort(parents(branch(id(%s)) and merge()) - branch(id(%s)), -rev)",
 
                branch_rev, branch_rev):
 
                abranch = repo.get_changeset(i).branch
 
                if abranch not in peerbranches:
 
                    n = 'branch:%s:%s' % (abranch, repo.get_changeset(abranch).raw_id)
 
                    peers.append((n, abranch))
 
                    peerbranches.add(abranch)
 
                for abranch in repo.get_changeset(i).branches:
 
                    if abranch not in peerbranches:
 
                        n = 'branch:%s:%s' % (abranch, repo.get_changeset(abranch).raw_id)
 
                        peers.append((n, abranch))
 
                        peerbranches.add(abranch)
 

	
 
        selected = None
 
        tiprev = repo.tags.get('tip')
 
        tipbranch = None
 

	
 
        branches = []
 
        for abranch, branchrev in repo.branches.iteritems():
 
            n = 'branch:%s:%s' % (abranch, branchrev)
 
            desc = abranch
 
            if branchrev == tiprev:
 
                tipbranch = abranch
 
                desc = '%s (current tip)' % desc
kallithea/lib/vcs/backends/base.py
Show inline comments
 
@@ -1029,24 +1029,29 @@ class EmptyChangeset(BaseChangeset):
 
        Returns raw string identifying this changeset, useful for web
 
        representation.
 
        """
 

	
 
        return self._empty_cs
 

	
 
    @LazyProperty
 
    def branch(self):
 
        from kallithea.lib.vcs.backends import get_backend
 
        return get_backend(self.alias).DEFAULT_BRANCH_NAME
 

	
 
    @LazyProperty
 
    def branches(self):
 
        from kallithea.lib.vcs.backends import get_backend
 
        return [get_backend(self.alias).DEFAULT_BRANCH_NAME]
 

	
 
    @LazyProperty
 
    def short_id(self):
 
        return self.raw_id[:12]
 

	
 
    def get_file_changeset(self, path):
 
        return self
 

	
 
    def get_file_content(self, path):
 
        return u''
 

	
 
    def get_file_size(self, path):
 
        return 0
 

	
kallithea/lib/vcs/backends/git/changeset.py
Show inline comments
 
@@ -82,31 +82,37 @@ class GitChangeset(BaseChangeset):
 
        return self.changed, self.added, self.removed
 

	
 
    @LazyProperty
 
    def tags(self):
 
        _tags = []
 
        for tname, tsha in self.repository.tags.iteritems():
 
            if tsha == self.raw_id:
 
                _tags.append(tname)
 
        return _tags
 

	
 
    @LazyProperty
 
    def branch(self):
 

	
 
        # Note: This function will return one branch name for the changeset -
 
        # that might not make sense in Git where branches() is a better match
 
        # for the basic model
 
        heads = self.repository._heads(reverse=False)
 

	
 
        ref = heads.get(self.raw_id)
 
        if ref:
 
            return safe_unicode(ref)
 

	
 
    @LazyProperty
 
    def branches(self):
 
        heads = self.repository._heads(reverse=True)
 
        return [b for b in heads if heads[b] == self.raw_id] # FIXME: Inefficient ... and returning None!
 

	
 
    def _fix_path(self, path):
 
        """
 
        Paths are stored without trailing slash so we need to get rid off it if
 
        needed.
 
        """
 
        if path.endswith('/'):
 
            path = path.rstrip('/')
 
        return path
 

	
 
    def _get_id_for_path(self, path):
 
        path = safe_str(path)
 
        # FIXME: Please, spare a couple of minutes and make those codes cleaner;
kallithea/lib/vcs/backends/hg/changeset.py
Show inline comments
 
@@ -32,24 +32,28 @@ class MercurialChangeset(BaseChangeset):
 
        self.revision = self._ctx._rev
 
        self.nodes = {}
 

	
 
    @LazyProperty
 
    def tags(self):
 
        return map(safe_unicode, self._ctx.tags())
 

	
 
    @LazyProperty
 
    def branch(self):
 
        return safe_unicode(self._ctx.branch())
 

	
 
    @LazyProperty
 
    def branches(self):
 
        return [safe_unicode(self._ctx.branch())]
 

	
 
    @LazyProperty
 
    def closesbranch(self):
 
        return self._ctx.closesbranch()
 

	
 
    @LazyProperty
 
    def obsolete(self):
 
        return self._ctx.obsolete()
 

	
 
    @LazyProperty
 
    def bumped(self):
 
        return self._ctx.bumped()
 

	
 
    @LazyProperty
kallithea/templates/changelog/changelog_table.html
Show inline comments
 
@@ -88,26 +88,28 @@
 
              %if cs.divergent:
 
                <span class="label label-divergent" title="Divergent">Divergent</span>
 
              %endif
 
              %if cs.extinct:
 
                <span class="label label-extinct" title="Extinct">Extinct</span>
 
              %endif
 
              %if cs.unstable:
 
                <span class="label label-unstable" title="Unstable">Unstable</span>
 
              %endif
 
              %if cs.phase:
 
                <span class="label label-phase" title="Phase">${cs.phase}</span>
 
              %endif
 
              %if show_branch and cs.branch:
 
                <span class="label label-branch" title="${_('Branch %s' % cs.branch)}">${h.link_to(cs.branch,h.url('changelog_home',repo_name=repo_name,branch=cs.branch))}</span>
 
              %if show_branch:
 
                %for branch in cs.branches:
 
                  <span class="label label-branch" title="${_('Branch %s' % branch)}">${h.link_to(branch,h.url('changelog_home',repo_name=repo_name,branch=branch))}</span>
 
                %endfor
 
              %endif
 
            </div>
 
          </div>
 
        </td>
 
      </tr>
 
      %endfor
 
    </tbody>
 
    </table>
 

	
 
<script type="text/javascript">
 
  $(document).ready(function() {
 
    $('#changesets .expand_commit').on('click',function(e){
kallithea/templates/changeset/changeset.html
Show inline comments
 
@@ -59,27 +59,27 @@ ${self.repo_context_bar('changelog', c.c
 
                        %if len(c.changeset.parents)>1:
 
                        <span class="label label-merge">${_('Merge')}</span>
 
                        %endif
 

	
 
                        %for book in c.changeset.bookmarks:
 
                        <span class="label label-bookmark" title="${_('Bookmark %s') % book}">${h.link_to(book,h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
 
                        %endfor
 

	
 
                        %for tag in c.changeset.tags:
 
                         <span class="label label-tag"  title="${_('Tag %s') % tag}">${h.link_to(tag,h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
 
                        %endfor
 

	
 
                        %if c.changeset.branch:
 
                         <span class="label label-branch" title="${_('Branch %s') % c.changeset.branch}">${h.link_to(c.changeset.branch,h.url('changelog_home',repo_name=c.repo_name,branch=c.changeset.branch))}</span>
 
                        %endif
 
                        %for branch in c.changeset.branches:
 
                          <span class="label label-branch" title="${_('Branch %s') % branch}">${h.link_to(branch,h.url('changelog_home',repo_name=c.repo_name,branch=branch))}</span>
 
                        %endfor
 
                    </span>
 

	
 
                    <div class="changes">
 
                        % if (len(c.changeset.affected_files) <= c.affected_files_cut_off) or c.fulldiff:
 
                         <span class="label deleted" title="${_('Removed')}">${len(c.changeset.removed)}</span>
 
                         <span class="label changed" title="${_('Changed')}">${len(c.changeset.changed)}</span>
 
                         <span class="label added" title="${_('Added')}">${len(c.changeset.added)}</span>
 
                        % else:
 
                         <span class="label deleted" title="${_('Affected %s files') % len(c.changeset.affected_files)}">!</span>
 
                         <span class="label changed" title="${_('Affected %s files') % len(c.changeset.affected_files)}">!</span>
 
                         <span class="label added"   title="${_('Affected %s files') % len(c.changeset.affected_files)}">!</span>
 
                        % endif
kallithea/templates/changeset/changeset_range.html
Show inline comments
 
@@ -81,27 +81,27 @@ ${self.repo_context_bar('changelog')}
 
                %endif
 
                %if h.is_hg(c.db_repo_scm_instance):
 
                  %for book in cs.bookmarks:
 
                  <span class="label label-bookmark" title="${_('Bookmark %s') % book}">
 
                     ${h.link_to(book,h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}
 
                  </span>
 
                  %endfor
 
                %endif
 
                %for tag in cs.tags:
 
                    <span class="label label-tag" title="${_('Tag %s') % tag}">
 
                    ${h.link_to(tag,h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}</span>
 
                %endfor
 
                %if cs.branch:
 
                <span class="label label-branch" title="${_('Branch %s') % cs.branch}">
 
                   ${h.link_to(cs.branch,h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}
 
                </span>
 
                %endif
 
                %for branch in cs.branches:
 
                  <span class="label label-branch" title="${_('Branch %s') % branch}">
 
                    ${h.link_to(branch,h.url('changeset_home',repo_name=c.cs_repo.repo_name,revision=cs.raw_id))}
 
                  </span>
 
                %endfor
 
              </span>
 
            </div>
 
          </div>
 
          <% a_rev, cs_rev, file_diff_data = c.changes[cs.raw_id] %>
 
          ${diff_block.diff_block(c.repo_name, 'rev', a_rev, a_rev,
 
                                  c.repo_name, 'rev', cs_rev, cs_rev, file_diff_data)}
 
        </div>
 
    %endfor
 
</div>
 
</%def>
kallithea/tests/vcs/test_branches.py
Show inline comments
 
@@ -44,24 +44,25 @@ class BranchesTestCaseMixin(_BackendTest
 
        # This check must not be removed to ensure the 'branches' LazyProperty
 
        # gets hit *before* the new 'foobar' branch got created:
 
        assert 'foobar' not in self.repo.branches
 
        self.imc.add(vcs.nodes.FileNode('docs/index.txt',
 
            content='Documentation\n'))
 
        foobar_tip = self.imc.commit(
 
            message=u'New branch: foobar',
 
            author=u'joe',
 
            branch='foobar',
 
        )
 
        assert 'foobar' in self.repo.branches
 
        assert foobar_tip.branch == 'foobar'
 
        assert foobar_tip.branches == ['foobar']
 

	
 
    def test_new_head(self):
 
        tip = self.repo.get_changeset()
 
        self.imc.add(vcs.nodes.FileNode('docs/index.txt',
 
            content='Documentation\n'))
 
        foobar_tip = self.imc.commit(
 
            message=u'New branch: foobar',
 
            author=u'joe',
 
            branch='foobar',
 
            parents=[tip],
 
        )
 
        self.imc.change(vcs.nodes.FileNode('docs/index.txt',
 
@@ -72,24 +73,25 @@ class BranchesTestCaseMixin(_BackendTest
 
            branch=foobar_tip.branch,
 
            parents=[foobar_tip],
 
        )
 

	
 
        newest_tip = self.imc.commit(
 
            message=u'Merged with %s' % foobar_tip.raw_id,
 
            author=u'joe',
 
            branch=self.backend_class.DEFAULT_BRANCH_NAME,
 
            parents=[newtip, foobar_tip],
 
        )
 

	
 
        assert newest_tip.branch == self.backend_class.DEFAULT_BRANCH_NAME
 
        assert newest_tip.branches == [self.backend_class.DEFAULT_BRANCH_NAME]
 

	
 
    def test_branch_with_slash_in_name(self):
 
        self.imc.add(vcs.nodes.FileNode('extrafile', content='Some data\n'))
 
        self.imc.commit(u'Branch with a slash!', author=u'joe',
 
            branch='issue/123')
 
        assert 'issue/123' in self.repo.branches
 

	
 
    def test_branch_with_slash_in_name_and_similar_without(self):
 
        self.imc.add(vcs.nodes.FileNode('extrafile', content='Some data\n'))
 
        self.imc.commit(u'Branch with a slash!', author=u'joe',
 
            branch='issue/123')
 
        self.imc.add(vcs.nodes.FileNode('extrafile II', content='Some data\n'))
kallithea/tests/vcs/test_changesets.py
Show inline comments
 
@@ -69,24 +69,25 @@ class _ChangesetsWithCommitsTestCaseixin
 
            }
 

	
 
    def test_new_branch(self):
 
        self.imc.add(vcs.nodes.FileNode('docs/index.txt',
 
            content='Documentation\n'))
 
        foobar_tip = self.imc.commit(
 
            message=u'New branch: foobar',
 
            author=u'joe',
 
            branch='foobar',
 
        )
 
        assert 'foobar' in self.repo.branches
 
        assert foobar_tip.branch == 'foobar'
 
        assert foobar_tip.branches == ['foobar']
 
        # 'foobar' should be the only branch that contains the new commit
 
        branch_tips = self.repo.branches.values()
 
        assert branch_tips.count(str(foobar_tip.raw_id)) == 1
 

	
 
    def test_new_head_in_default_branch(self):
 
        tip = self.repo.get_changeset()
 
        self.imc.add(vcs.nodes.FileNode('docs/index.txt',
 
            content='Documentation\n'))
 
        foobar_tip = self.imc.commit(
 
            message=u'New branch: foobar',
 
            author=u'joe',
 
            branch='foobar',
 
@@ -100,24 +101,25 @@ class _ChangesetsWithCommitsTestCaseixin
 
            branch=foobar_tip.branch,
 
            parents=[foobar_tip],
 
        )
 

	
 
        newest_tip = self.imc.commit(
 
            message=u'Merged with %s' % foobar_tip.raw_id,
 
            author=u'joe',
 
            branch=self.backend_class.DEFAULT_BRANCH_NAME,
 
            parents=[newtip, foobar_tip],
 
        )
 

	
 
        assert newest_tip.branch == self.backend_class.DEFAULT_BRANCH_NAME
 
        assert newest_tip.branches == [self.backend_class.DEFAULT_BRANCH_NAME]
 

	
 
    def test_get_changesets_respects_branch_name(self):
 
        tip = self.repo.get_changeset()
 
        self.imc.add(vcs.nodes.FileNode('docs/index.txt',
 
            content='Documentation\n'))
 
        doc_changeset = self.imc.commit(
 
            message=u'New branch: docs',
 
            author=u'joe',
 
            branch='docs',
 
        )
 
        self.imc.add(vcs.nodes.FileNode('newfile', content=''))
 
        self.imc.commit(
kallithea/tests/vcs/test_git.py
Show inline comments
 
@@ -300,34 +300,37 @@ class TestGitChangeset(object):
 
        index = api.get_node('index.rst')
 
        assert index is chset.get_node('docs/api/index.rst')
 
        assert index is chset.get_node('docs') \
 
                             .get_node('api') \
 
                             .get_node('index.rst')
 

	
 
    def test_branch_and_tags(self):
 
        # Those tests seem to show wrong results:
 
        # in Git, only heads have a branch - most changesets don't
 
        rev0 = self.repo.revisions[0]
 
        chset0 = self.repo.get_changeset(rev0)
 
        assert chset0.branch is None # should be 'master'?
 
        assert chset0.branches == [] # should be 'master'?
 
        assert chset0.tags == []
 

	
 
        rev10 = self.repo.revisions[10]
 
        chset10 = self.repo.get_changeset(rev10)
 
        assert chset10.branch is None # should be 'master'?
 
        assert chset10.branches == [] # should be 'master'?
 
        assert chset10.tags == []
 

	
 
        rev44 = self.repo.revisions[44]
 
        chset44 = self.repo.get_changeset(rev44)
 
        assert chset44.branch is None # should be 'web-branch'?
 
        assert chset44.branches == [] # should be 'web-branch'?
 

	
 
        tip = self.repo.get_changeset('tip')
 
        assert 'tip' not in tip.tags # it should be?
 
        assert not tip.tags # how it is!
 

	
 
    def _test_slices(self, limit, offset):
 
        count = self.repo.count()
 
        changesets = self.repo.get_changesets(limit=limit, offset=offset)
 
        idx = 0
 
        for changeset in changesets:
 
            rev = offset + idx
 
            idx += 1
kallithea/tests/vcs/test_hg.py
Show inline comments
 
@@ -311,32 +311,35 @@ class TestMercurialChangeset(object):
 
        root = chset.root
 
        docs = root.get_node('docs')
 
        assert docs is chset.get_node('docs')
 
        api = docs.get_node('api')
 
        assert api is chset.get_node('docs/api')
 
        index = api.get_node('index.rst')
 
        assert index is chset.get_node('docs/api/index.rst')
 
        assert index is chset.get_node('docs').get_node('api').get_node('index.rst')
 

	
 
    def test_branch_and_tags(self):
 
        chset0 = self.repo.get_changeset(0)
 
        assert chset0.branch == 'default'
 
        assert chset0.branches == ['default']
 
        assert chset0.tags == []
 

	
 
        chset10 = self.repo.get_changeset(10)
 
        assert chset10.branch == 'default'
 
        assert chset10.branches == ['default']
 
        assert chset10.tags == []
 

	
 
        chset44 = self.repo.get_changeset(44)
 
        assert chset44.branch == 'web'
 
        assert chset44.branches == ['web']
 

	
 
        tip = self.repo.get_changeset('tip')
 
        assert 'tip' in tip.tags
 

	
 
    def _test_file_size(self, revision, path, size):
 
        node = self.repo.get_changeset(revision).get_node(path)
 
        assert node.is_file()
 
        assert node.size == size
 

	
 
    def test_file_size(self):
 
        to_check = (
 
            (10, 'setup.py', 1068),
0 comments (0 inline, 0 general)