filelog.py
139 lines
| 4.3 KiB
| text/x-python
|
PythonLexer
/ mercurial / filelog.py
|
r1089 | # filelog.py - file history class for mercurial | ||
# | ||||
|
r4635 | # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | ||
|
r1089 | # | ||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r1089 | |||
|
r25948 | from __future__ import absolute_import | ||
import re | ||||
import struct | ||||
from . import ( | ||||
error, | ||||
mdiff, | ||||
revlog, | ||||
) | ||||
|
r1089 | |||
|
r14074 | _mdre = re.compile('\1\n') | ||
|
r22421 | def parsemeta(text): | ||
|
r32124 | """return (metadatadict, metadatasize)""" | ||
|
r14074 | # text can be buffer, so we can't use .startswith or .index | ||
if text[:2] != '\1\n': | ||||
|
r22422 | return None, None | ||
|
r14074 | s = _mdre.search(text, 2).start() | ||
mtext = text[2:s] | ||||
meta = {} | ||||
for l in mtext.splitlines(): | ||||
|
r13240 | k, v = l.split(": ", 1) | ||
|
r14074 | meta[k] = v | ||
|
r22422 | return meta, (s + 2) | ||
|
r14074 | |||
|
r22420 | def packmeta(meta, text): | ||
|
r34025 | keys = sorted(meta) | ||
|
r22420 | metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys) | ||
return "\1\n%s\1\n%s" % (metatext, text) | ||||
|
r13240 | |||
|
r22596 | def _censoredtext(text): | ||
m, offs = parsemeta(text) | ||||
|
r24117 | return m and "censored" in m | ||
|
r22596 | |||
|
r7634 | class filelog(revlog.revlog): | ||
|
r4258 | def __init__(self, opener, path): | ||
|
r19148 | super(filelog, self).__init__(opener, | ||
|
r8531 | "/".join(("data", path + ".i"))) | ||
|
r35583 | # full name of the user visible file, relative to the repository root | ||
self.filename = path | ||||
|
r1089 | |||
def read(self, node): | ||||
t = self.revision(node) | ||||
if not t.startswith('\1\n'): | ||||
return t | ||||
|
r2579 | s = t.index('\1\n', 2) | ||
|
r10282 | return t[s + 2:] | ||
|
r1089 | |||
def add(self, text, meta, transaction, link, p1=None, p2=None): | ||||
if meta or text.startswith('\1\n'): | ||||
|
r22420 | text = packmeta(meta, text) | ||
|
r1089 | return self.addrevision(text, transaction, link, p1, p2) | ||
|
r1116 | def renamed(self, node): | ||
|
r7634 | if self.parents(node)[0] != revlog.nullid: | ||
|
r1116 | return False | ||
|
r13240 | t = self.revision(node) | ||
|
r22421 | m = parsemeta(t)[0] | ||
|
r5915 | if m and "copy" in m: | ||
|
r7634 | return (m["copy"], revlog.bin(m["copyrev"])) | ||
|
r1116 | return False | ||
|
r2898 | def size(self, rev): | ||
"""return the size of a given revision""" | ||||
# for revisions with renames, we have to go the slow way | ||||
node = self.node(rev) | ||||
if self.renamed(node): | ||||
return len(self.read(node)) | ||||
|
r24118 | if self.iscensored(rev): | ||
|
r22597 | return 0 | ||
|
r2898 | |||
|
r11540 | # XXX if self.read(node).startswith("\1\n"), this returns (size+4) | ||
|
r19148 | return super(filelog, self).size(rev) | ||
|
r2898 | |||
|
r2887 | def cmp(self, node, text): | ||
|
r11539 | """compare text with a given file revision | ||
returns True if text is different than what is stored. | ||||
""" | ||||
|
r2887 | |||
|
r11541 | t = text | ||
if text.startswith('\1\n'): | ||||
t = '\1\n\1\n' + text | ||||
|
r19148 | samehashes = not super(filelog, self).cmp(node, t) | ||
|
r11541 | if samehashes: | ||
return False | ||||
|
r22597 | # censored files compare against the empty file | ||
|
r24118 | if self.iscensored(self.rev(node)): | ||
|
r22597 | return text != '' | ||
|
r11541 | # renaming a file produces a different hash, even if the data | ||
# remains unchanged. Check if it's the case (slow): | ||||
if self.renamed(node): | ||||
|
r2887 | t2 = self.read(node) | ||
|
r2895 | return t2 != text | ||
|
r2887 | |||
|
r11541 | return True | ||
|
r14287 | |||
|
r30584 | def checkhash(self, text, node, p1=None, p2=None, rev=None): | ||
|
r22596 | try: | ||
|
r30584 | super(filelog, self).checkhash(text, node, p1=p1, p2=p2, rev=rev) | ||
|
r22596 | except error.RevlogError: | ||
if _censoredtext(text): | ||||
|
r24190 | raise error.CensoredNodeError(self.indexfile, node, text) | ||
|
r22596 | raise | ||
|
r24118 | def iscensored(self, rev): | ||
|
r22597 | """Check if a file revision is censored.""" | ||
|
r23858 | return self.flags(rev) & revlog.REVIDX_ISCENSORED | ||
|
r24255 | |||
def _peek_iscensored(self, baserev, delta, flush): | ||||
"""Quickly check if a delta produces a censored revision.""" | ||||
# Fragile heuristic: unless new file meta keys are added alphabetically | ||||
# preceding "censored", all censored revisions are prefixed by | ||||
# "\1\ncensored:". A delta producing such a censored revision must be a | ||||
# full-replacement delta, so we inspect the first and only patch in the | ||||
# delta for this prefix. | ||||
hlen = struct.calcsize(">lll") | ||||
if len(delta) <= hlen: | ||||
return False | ||||
oldlen = self.rawsize(baserev) | ||||
newlen = len(delta) - hlen | ||||
if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen): | ||||
return False | ||||
add = "\1\ncensored:" | ||||
addlen = len(add) | ||||
return newlen >= addlen and delta[hlen:hlen + addlen] == add | ||||