bzr branch
http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
| 1
by mbp at sourcefrog import from baz patch-364 | 1 | #! /usr/bin/env python
 | 
| 2 | # -*- coding: UTF-8 -*-
 | |
| 3 | ||
| 4 | # This program is free software; you can redistribute it and/or modify
 | |
| 5 | # it under the terms of the GNU General Public License as published by
 | |
| 6 | # the Free Software Foundation; either version 2 of the License, or
 | |
| 7 | # (at your option) any later version.
 | |
| 8 | ||
| 9 | # This program is distributed in the hope that it will be useful,
 | |
| 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| 12 | # GNU General Public License for more details.
 | |
| 13 | ||
| 14 | # You should have received a copy of the GNU General Public License
 | |
| 15 | # along with this program; if not, write to the Free Software
 | |
| 16 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | |
| 17 | ||
| 18 | from sets import Set | |
| 19 | ||
| 20 | from trace import mutter | |
| 356
by Martin Pool - pychecker fixes in bzrlib.diff | 21 | from errors import BzrError | 
| 1
by mbp at sourcefrog import from baz patch-364 | 22 | |
| 23 | ||
| 24 | def diff_trees(old_tree, new_tree): | |
| 25 | """Compute diff between two trees. | |
| 26 | ||
| 27 |     They may be in different branches and may be working or historical
 | |
| 28 |     trees.
 | |
| 29 | ||
| 30 |     Yields a sequence of (state, id, old_name, new_name, kind).
 | |
| 31 |     Each filename and each id is listed only once.
 | |
| 32 |     """
 | |
| 33 | ||
| 34 |     ## TODO: Compare files before diffing; only mention those that have changed
 | |
| 35 | ||
| 36 |     ## TODO: Set nice names in the headers, maybe include diffstat
 | |
| 37 | ||
| 38 |     ## TODO: Perhaps make this a generator rather than using
 | |
| 39 |     ## a callback object?
 | |
| 40 | ||
| 41 |     ## TODO: Allow specifying a list of files to compare, rather than
 | |
| 42 |     ## doing the whole tree?  (Not urgent.)
 | |
| 43 | ||
| 44 |     ## TODO: Allow diffing any two inventories, not just the
 | |
| 45 |     ## current one against one.  We mgiht need to specify two
 | |
| 46 |     ## stores to look for the files if diffing two branches.  That
 | |
| 47 |     ## might imply this shouldn't be primarily a Branch method.
 | |
| 48 | ||
| 49 |     ## XXX: This doesn't report on unknown files; that can be done
 | |
| 50 |     ## from a separate method.
 | |
| 51 | ||
| 52 | old_it = old_tree.list_files() | |
| 53 | new_it = new_tree.list_files() | |
| 54 | ||
| 55 | def next(it): | |
| 56 | try: | |
| 57 | return it.next() | |
| 58 | except StopIteration: | |
| 59 | return None | |
| 60 | ||
| 61 | old_item = next(old_it) | |
| 62 | new_item = next(new_it) | |
| 63 | ||
| 64 |     # We step through the two sorted iterators in parallel, trying to
 | |
| 65 |     # keep them lined up.
 | |
| 66 | ||
| 67 | while (old_item != None) or (new_item != None): | |
| 68 |         # OK, we still have some remaining on both, but they may be
 | |
| 69 |         # out of step.        
 | |
| 70 | if old_item != None: | |
| 71 | old_name, old_class, old_kind, old_id = old_item | |
| 72 | else: | |
| 73 | old_name = None | |
| 74 | ||
| 75 | if new_item != None: | |
| 76 | new_name, new_class, new_kind, new_id = new_item | |
| 77 | else: | |
| 78 | new_name = None | |
| 79 | ||
| 80 | mutter(" diff pairwise %r" % (old_item,)) | |
| 81 | mutter(" %r" % (new_item,)) | |
| 82 | ||
| 83 | if old_item: | |
| 84 |             # can't handle the old tree being a WorkingTree
 | |
| 85 | assert old_class == 'V' | |
| 86 | ||
| 87 | if new_item and (new_class != 'V'): | |
| 88 | yield new_class, None, None, new_name, new_kind | |
| 89 | new_item = next(new_it) | |
| 90 | elif (not new_item) or (old_item and (old_name < new_name)): | |
| 91 | mutter(" extra entry in old-tree sequence") | |
| 92 | if new_tree.has_id(old_id): | |
| 93 |                 # will be mentioned as renamed under new name
 | |
| 94 |                 pass
 | |
| 95 | else: | |
| 96 | yield 'D', old_id, old_name, None, old_kind | |
| 97 | old_item = next(old_it) | |
| 98 | elif (not old_item) or (new_item and (new_name < old_name)): | |
| 99 | mutter(" extra entry in new-tree sequence") | |
| 100 | if old_tree.has_id(new_id): | |
| 101 | yield 'R', new_id, old_tree.id2path(new_id), new_name, new_kind | |
| 102 | else: | |
| 103 | yield 'A', new_id, None, new_name, new_kind | |
| 104 | new_item = next(new_it) | |
| 105 | elif old_id != new_id: | |
| 106 | assert old_name == new_name | |
| 107 |             # both trees have a file of this name, but it is not the
 | |
| 108 |             # same file.  in other words, the old filename has been
 | |
| 109 |             # overwritten by either a newly-added or a renamed file.
 | |
| 110 |             # (should we return something about the overwritten file?)
 | |
| 111 | if old_tree.has_id(new_id): | |
| 112 |                 # renaming, overlying a deleted file
 | |
| 113 | yield 'R', new_id, old_tree.id2path(new_id), new_name, new_kind | |
| 114 | else: | |
| 115 | yield 'A', new_id, None, new_name, new_kind | |
| 116 | ||
| 117 | new_item = next(new_it) | |
| 118 | old_item = next(old_it) | |
| 119 | else: | |
| 120 | assert old_id == new_id | |
| 178
by mbp at sourcefrog - Use a non-null file_id for the branch root directory. At the moment | 121 | assert old_id != None | 
| 1
by mbp at sourcefrog import from baz patch-364 | 122 | assert old_name == new_name | 
| 123 | assert old_kind == new_kind | |
| 124 | ||
| 125 | if old_kind == 'directory': | |
| 126 | yield '.', new_id, old_name, new_name, new_kind | |
| 127 | elif old_tree.get_file_size(old_id) != new_tree.get_file_size(old_id): | |
| 128 | mutter(" file size has changed, must be different") | |
| 129 | yield 'M', new_id, old_name, new_name, new_kind | |
| 130 | elif old_tree.get_file_sha1(old_id) == new_tree.get_file_sha1(old_id): | |
| 131 | mutter(" SHA1 indicates they're identical") | |
| 132 |                 ## assert compare_files(old_tree.get_file(i), new_tree.get_file(i))
 | |
| 133 | yield '.', new_id, old_name, new_name, new_kind | |
| 134 | else: | |
| 135 | mutter(" quick compare shows different") | |
| 136 | yield 'M', new_id, old_name, new_name, new_kind | |
| 137 | ||
| 138 | new_item = next(new_it) | |
| 139 | old_item = next(old_it) | |
| 140 | ||
| 141 | ||
| 329
by Martin Pool - refactor command functions into command classes | 142 | |
| 143 | def show_diff(b, revision, file_list): | |
| 356
by Martin Pool - pychecker fixes in bzrlib.diff | 144 | import difflib, sys, types | 
| 329
by Martin Pool - refactor command functions into command classes | 145 | |
| 146 | if revision == None: | |
| 147 | old_tree = b.basis_tree() | |
| 148 | else: | |
| 149 | old_tree = b.revision_tree(b.lookup_revision(revision)) | |
| 150 | ||
| 151 | new_tree = b.working_tree() | |
| 152 | ||
| 153 |     # TODO: Options to control putting on a prefix or suffix, perhaps as a format string
 | |
| 154 | old_label = '' | |
| 155 | new_label = '' | |
| 156 | ||
| 157 | DEVNULL = '/dev/null' | |
| 158 |     # Windows users, don't panic about this filename -- it is a
 | |
| 159 |     # special signal to GNU patch that the file should be created or
 | |
| 160 |     # deleted respectively.
 | |
| 161 | ||
| 162 |     # TODO: Generation of pseudo-diffs for added/deleted files could
 | |
| 163 |     # be usefully made into a much faster special case.
 | |
| 164 | ||
| 165 |     # TODO: Better to return them in sorted order I think.
 | |
| 166 | ||
| 167 | if file_list: | |
| 168 | file_list = [b.relpath(f) for f in file_list] | |
| 169 | ||
| 170 |     # FIXME: If given a file list, compare only those files rather
 | |
| 171 |     # than comparing everything and then throwing stuff away.
 | |
| 172 | ||
| 173 | for file_state, fid, old_name, new_name, kind in diff_trees(old_tree, new_tree): | |
| 174 | ||
| 175 | if file_list and (new_name not in file_list): | |
| 176 |             continue
 | |
| 177 | ||
| 178 |         # Don't show this by default; maybe do it if an option is passed
 | |
| 179 |         # idlabel = '      {%s}' % fid
 | |
| 180 | idlabel = '' | |
| 181 | ||
| 182 |         # FIXME: Something about the diff format makes patch unhappy
 | |
| 183 |         # with newly-added files.
 | |
| 184 | ||
| 185 | def diffit(oldlines, newlines, **kw): | |
| 186 | ||
| 187 |             # FIXME: difflib is wrong if there is no trailing newline.
 | |
| 188 |             # The syntax used by patch seems to be "\ No newline at
 | |
| 189 |             # end of file" following the last diff line from that
 | |
| 190 |             # file.  This is not trivial to insert into the
 | |
| 191 |             # unified_diff output and it might be better to just fix
 | |
| 192 |             # or replace that function.
 | |
| 193 | ||
| 194 |             # In the meantime we at least make sure the patch isn't
 | |
| 195 |             # mangled.
 | |
| 196 | ||
| 197 | ||
| 198 |             # Special workaround for Python2.3, where difflib fails if
 | |
| 199 |             # both sequences are empty.
 | |
| 200 | if not oldlines and not newlines: | |
| 201 |                 return
 | |
| 202 | ||
| 203 | nonl = False | |
| 204 | ||
| 205 | if oldlines and (oldlines[-1][-1] != '\n'): | |
| 206 | oldlines[-1] += '\n' | |
| 207 | nonl = True | |
| 208 | if newlines and (newlines[-1][-1] != '\n'): | |
| 209 | newlines[-1] += '\n' | |
| 210 | nonl = True | |
| 211 | ||
| 212 | ud = difflib.unified_diff(oldlines, newlines, **kw) | |
| 213 | sys.stdout.writelines(ud) | |
| 214 | if nonl: | |
| 215 | print "\\ No newline at end of file" | |
| 216 | sys.stdout.write('\n') | |
| 217 | ||
| 218 | if file_state in ['.', '?', 'I']: | |
| 219 |             continue
 | |
| 220 | elif file_state == 'A': | |
| 221 | print '*** added %s %r' % (kind, new_name) | |
| 222 | if kind == 'file': | |
| 223 | diffit([], | |
| 224 | new_tree.get_file(fid).readlines(), | |
| 225 | fromfile=DEVNULL, | |
| 226 | tofile=new_label + new_name + idlabel) | |
| 227 | elif file_state == 'D': | |
| 228 | assert isinstance(old_name, types.StringTypes) | |
| 229 | print '*** deleted %s %r' % (kind, old_name) | |
| 230 | if kind == 'file': | |
| 231 | diffit(old_tree.get_file(fid).readlines(), [], | |
| 232 | fromfile=old_label + old_name + idlabel, | |
| 233 | tofile=DEVNULL) | |
| 234 | elif file_state in ['M', 'R']: | |
| 235 | if file_state == 'M': | |
| 236 | assert kind == 'file' | |
| 237 | assert old_name == new_name | |
| 238 | print '*** modified %s %r' % (kind, new_name) | |
| 239 | elif file_state == 'R': | |
| 240 | print '*** renamed %s %r => %r' % (kind, old_name, new_name) | |
| 241 | ||
| 242 | if kind == 'file': | |
| 243 | diffit(old_tree.get_file(fid).readlines(), | |
| 244 | new_tree.get_file(fid).readlines(), | |
| 245 | fromfile=old_label + old_name + idlabel, | |
| 246 | tofile=new_label + new_name) | |
| 247 | else: | |
| 356
by Martin Pool - pychecker fixes in bzrlib.diff | 248 | raise BzrError("can't represent state %s {%s}" % (file_state, fid)) | 
| 329
by Martin Pool - refactor command functions into command classes | 249 | |
| 250 | ||
| 379
by Martin Pool - Simpler compare_inventories() to possibly replace diff_trees | 251 | |
| 252 | class TreeDelta: | |
| 253 | """Describes changes from one tree to another. | |
| 254 | ||
| 255 |     Contains four lists:
 | |
| 256 | ||
| 257 |     added
 | |
| 258 |         (path, id)
 | |
| 259 |     removed
 | |
| 260 |         (path, id)
 | |
| 261 |     renamed
 | |
| 262 |         (oldpath, newpath, id)
 | |
| 263 |     modified
 | |
| 264 |         (path, id)
 | |
| 265 | ||
| 266 |     A path may occur in more than one list if it was e.g. deleted
 | |
| 267 |     under an old id and renamed into place in a new id.
 | |
| 268 | ||
| 269 |     Files are listed in either modified or renamed, not both.  In
 | |
| 270 |     other words, renamed files may also be modified.
 | |
| 271 |     """
 | |
| 272 | def __init__(self): | |
| 273 | self.added = [] | |
| 274 | self.removed = [] | |
| 275 | self.renamed = [] | |
| 276 | self.modified = [] | |
| 277 | ||
| 278 | ||
| 279 | def compare_inventories(old_inv, new_inv): | |
| 280 | """Return a TreeDelta object describing changes between inventories. | |
| 281 | ||
| 282 |     This only describes changes in the shape of the tree, not the
 | |
| 283 |     actual texts.
 | |
| 284 | ||
| 285 |     This is an alternative to diff_trees() and should probably
 | |
| 286 |     eventually replace it.
 | |
| 287 |     """
 | |
| 288 | old_ids = old_inv.id_set() | |
| 289 | new_ids = new_inv.id_set() | |
| 290 | delta = TreeDelta() | |
| 291 | ||
| 292 | delta.removed = [(old_inv.id2path(fid), fid) for fid in (old_ids - new_ids)] | |
| 293 | delta.removed.sort() | |
| 294 | ||
| 295 | delta.added = [(new_inv.id2path(fid), fid) for fid in (new_ids - old_ids)] | |
| 296 | delta.added.sort() | |
| 297 | ||
| 298 | for fid in old_ids & new_ids: | |
| 299 | old_ie = old_inv[fid] | |
| 300 | new_ie = new_inv[fid] | |
| 301 | old_path = old_inv.id2path(fid) | |
| 302 | new_path = new_inv.id2path(fid) | |
| 303 | ||
| 304 | if old_path != new_path: | |
| 305 | delta.renamed.append((old_path, new_path, fid)) | |
| 306 | elif old_ie.text_sha1 != new_ie.text_sha1: | |
| 307 | delta.modified.append((new_path, fid)) | |
| 308 | ||
| 309 | delta.modified.sort() | |
| 310 | delta.renamed.sort() | |
| 311 | ||
| 312 | return delta |