/breezy-svn/trunk

To get this branch, use:
bzr branch https://code.breezy-vcs.org/breezy-svn/trunk
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# Copyright (C) 2005-2009 Jelmer Vernooij <jelmer@samba.org>

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.


"""Utility functions for dealing with changes dictionaries as return by Subversions' log functions."""

from __future__ import absolute_import

from subvertpy import NODE_DIR


from breezy.sixish import text_type


REV0_CHANGES = {u"": ('A', None, -1, NODE_DIR)}


def path_is_child(branch_path, path):
    """Check whether path is or is under branch_path."""
    return (branch_path == u"" or
            branch_path == path or
            path.startswith(branch_path+u"/"))


def find_prev_location(paths, branch_path, revnum):
    """Find the previous location at which branch_path can be found.

    :note: If branch_path wasn't copied, this will return revnum-1 as the
        previous revision.
    """
    assert isinstance(revnum, int)
    assert isinstance(branch_path, text_type)
    if revnum == 0:
        assert branch_path == u""
        return None
    # If there are no special cases, just go try the
    # next revnum in history
    revnum -= 1

    if branch_path == u"":
        return (branch_path, revnum)

    # Make sure we get the right location for next time, if
    # the branch itself was copied
    if (paths.has_key(branch_path) and
        paths[branch_path][0] in ('R', 'A')):
        if paths[branch_path][1] is None:
            return None # Was added here
        revnum = paths[branch_path][2]
        assert isinstance(paths[branch_path][1], text_type)
        branch_path = paths[branch_path][1]
        return (branch_path, revnum)

    # Make sure we get the right location for the next time if
    # one of the parents changed

    # Path names need to be sorted so the longer paths
    # override the shorter ones
    for p in sorted(paths.keys(), reverse=True):
        if paths[p][0] == 'M':
            continue
        if branch_path.startswith(p+u"/"):
            if paths[p][0] not in ('A', 'R'):
                raise AssertionError("Parent %r wasn't added" % (
                    p, paths[p][0]))
            if paths[p][1] is None:
                raise AssertionError(
                    "Empty parent %r added, but child %r wasn't added !?" % (
                        p, branch_path))
            revnum = paths[p][2]
            branch_path = paths[p][1] + branch_path[len(p):]
            return (branch_path.lstrip(u"/"), revnum)

    return (branch_path, revnum)


def changes_path(changes, path, parents=False):
    """Check if one of the specified changes applies
    to path or one of its children.

    :param parents: Whether to consider a parent moving a change.
    """
    for p in changes:
        assert isinstance(p, text_type)
        if path_is_child(path, p):
            return True
        if parents and path.startswith(p+u"/") and changes[p][0] in ('R', 'A'):
            return True
    return False


def changes_children(changes, path):
    """Check if one of the specified changes applies to
    one of paths children.

    :note: Does not consider changes to path itself.
    """
    for p in changes:
        assert isinstance(p, text_type)
        if path_is_child(path, p) and path != p:
            return True
    return False


def changes_root(paths):
    """Find the root path that was changed.

    If there is more than one root, returns None
    """
    if paths == []:
        return None
    paths = sorted(paths)
    root = paths[0]
    for p in paths[1:]:
        if p.startswith(u"%s/" % root): # new path is child of root
            continue
        elif root.startswith(u"%s/" % p): # new path is parent of root
            root = p
        else:
            if u"" in paths:
                return u""
            return None # Mismatch
    return root

def apply_reverse_changes(branches, changes):
    """Apply the specified changes on a set of branch names in reverse.
    (E.g. as if we were applying the reverse of a delta)

    :return: [(new_name, old_name, new_rev, kind)]
    :note: new_name is the name before these changes,
           old_name is the name after the changes.
           new_rev is the revision that the changes were copied from
           (new_name), or -1 if the previous revnum
    """
    branches = set(branches)
    for p in sorted(changes):
        (action, cf, cr, kind) = changes[p]
        if action == 'D':
            for b in list(branches):
                if path_is_child(p, b):
                    branches.remove(b)
                    yield b, None, -1, kind
        elif cf is not None:
            for b in list(branches):
                if path_is_child(p, b):
                    old_b = rebase_path(b, p, cf)
                    yield b, old_b, cr, kind
                    branches.remove(b)
                    branches.add(old_b)


def rebase_path(path, orig_parent, new_parent):
    """Rebase a path on a different parent."""
    return (new_parent+u"/"+path[len(orig_parent):].strip(u"/")).strip(u"/")


def changes_outside_branch_path(branch_path, paths):
    """Check whether there are any paths that are not under branch_path."""
    for p in paths:
        if not path_is_child(branch_path, p):
            return True
    return False


def under_prefixes(path, prefixes):
    """Check if path is under one of prefixes."""
    if prefixes is None or u"" in prefixes:
        return True
    return any([x for x in prefixes if path_is_child(x, path)])


def common_prefix(paths):
    prefix = u""
    if paths == []:
        return u""
    # Find a common prefix
    parts = paths[0].split(u"/")
    for i in range(len(parts)+1):
        for j in paths:
            if j.split(u"/")[:i] != parts[:i]:
                return prefix
        prefix = u"/".join(parts[:i])
    return prefix