mercurial/dirstate.py
changeset 1089 142b5d5ec9cc
parent 1072 05dc7aba22eb
child 1094 221b5252864c
equal deleted inserted replaced
1088:39b916b1d8e4 1089:142b5d5ec9cc
       
     1 """
       
     2 dirstate.py - working directory tracking for mercurial
       
     3 
       
     4 Copyright 2005 Matt Mackall <mpm@selenic.com>
       
     5 
       
     6 This software may be used and distributed according to the terms
       
     7 of the GNU General Public License, incorporated herein by reference.
       
     8 """
       
     9 
       
    10 import sys, struct, os
       
    11 from revlog import *
       
    12 from demandload import *
       
    13 demandload(globals(), "time bisect stat util")
       
    14 
       
    15 class dirstate:
       
    16     def __init__(self, opener, ui, root):
       
    17         self.opener = opener
       
    18         self.root = root
       
    19         self.dirty = 0
       
    20         self.ui = ui
       
    21         self.map = None
       
    22         self.pl = None
       
    23         self.copies = {}
       
    24         self.ignorefunc = None
       
    25 
       
    26     def wjoin(self, f):
       
    27         return os.path.join(self.root, f)
       
    28 
       
    29     def getcwd(self):
       
    30         cwd = os.getcwd()
       
    31         if cwd == self.root: return ''
       
    32         return cwd[len(self.root) + 1:]
       
    33 
       
    34     def ignore(self, f):
       
    35         if not self.ignorefunc:
       
    36             bigpat = []
       
    37             try:
       
    38                 l = file(self.wjoin(".hgignore"))
       
    39                 for pat in l:
       
    40                     p = pat.rstrip()
       
    41                     if p:
       
    42                         try:
       
    43                             re.compile(p)
       
    44                         except:
       
    45                             self.ui.warn("ignoring invalid ignore"
       
    46                                          + " regular expression '%s'\n" % p)
       
    47                         else:
       
    48                             bigpat.append(p)
       
    49             except IOError: pass
       
    50 
       
    51             if bigpat:
       
    52                 s = "(?:%s)" % (")|(?:".join(bigpat))
       
    53                 r = re.compile(s)
       
    54                 self.ignorefunc = r.search
       
    55             else:
       
    56                 self.ignorefunc = util.never
       
    57 
       
    58         return self.ignorefunc(f)
       
    59 
       
    60     def __del__(self):
       
    61         if self.dirty:
       
    62             self.write()
       
    63 
       
    64     def __getitem__(self, key):
       
    65         try:
       
    66             return self.map[key]
       
    67         except TypeError:
       
    68             self.read()
       
    69             return self[key]
       
    70 
       
    71     def __contains__(self, key):
       
    72         if not self.map: self.read()
       
    73         return key in self.map
       
    74 
       
    75     def parents(self):
       
    76         if not self.pl:
       
    77             self.read()
       
    78         return self.pl
       
    79 
       
    80     def markdirty(self):
       
    81         if not self.dirty:
       
    82             self.dirty = 1
       
    83 
       
    84     def setparents(self, p1, p2=nullid):
       
    85         self.markdirty()
       
    86         self.pl = p1, p2
       
    87 
       
    88     def state(self, key):
       
    89         try:
       
    90             return self[key][0]
       
    91         except KeyError:
       
    92             return "?"
       
    93 
       
    94     def read(self):
       
    95         if self.map is not None: return self.map
       
    96 
       
    97         self.map = {}
       
    98         self.pl = [nullid, nullid]
       
    99         try:
       
   100             st = self.opener("dirstate").read()
       
   101             if not st: return
       
   102         except: return
       
   103 
       
   104         self.pl = [st[:20], st[20: 40]]
       
   105 
       
   106         pos = 40
       
   107         while pos < len(st):
       
   108             e = struct.unpack(">cllll", st[pos:pos+17])
       
   109             l = e[4]
       
   110             pos += 17
       
   111             f = st[pos:pos + l]
       
   112             if '\0' in f:
       
   113                 f, c = f.split('\0')
       
   114                 self.copies[f] = c
       
   115             self.map[f] = e[:4]
       
   116             pos += l
       
   117 
       
   118     def copy(self, source, dest):
       
   119         self.read()
       
   120         self.markdirty()
       
   121         self.copies[dest] = source
       
   122 
       
   123     def copied(self, file):
       
   124         return self.copies.get(file, None)
       
   125 
       
   126     def update(self, files, state, **kw):
       
   127         ''' current states:
       
   128         n  normal
       
   129         m  needs merging
       
   130         r  marked for removal
       
   131         a  marked for addition'''
       
   132 
       
   133         if not files: return
       
   134         self.read()
       
   135         self.markdirty()
       
   136         for f in files:
       
   137             if state == "r":
       
   138                 self.map[f] = ('r', 0, 0, 0)
       
   139             else:
       
   140                 s = os.stat(os.path.join(self.root, f))
       
   141                 st_size = kw.get('st_size', s.st_size)
       
   142                 st_mtime = kw.get('st_mtime', s.st_mtime)
       
   143                 self.map[f] = (state, s.st_mode, st_size, st_mtime)
       
   144 
       
   145     def forget(self, files):
       
   146         if not files: return
       
   147         self.read()
       
   148         self.markdirty()
       
   149         for f in files:
       
   150             try:
       
   151                 del self.map[f]
       
   152             except KeyError:
       
   153                 self.ui.warn("not in dirstate: %s!\n" % f)
       
   154                 pass
       
   155 
       
   156     def clear(self):
       
   157         self.map = {}
       
   158         self.markdirty()
       
   159 
       
   160     def write(self):
       
   161         st = self.opener("dirstate", "w")
       
   162         st.write("".join(self.pl))
       
   163         for f, e in self.map.items():
       
   164             c = self.copied(f)
       
   165             if c:
       
   166                 f = f + "\0" + c
       
   167             e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
       
   168             st.write(e + f)
       
   169         self.dirty = 0
       
   170 
       
   171     def filterfiles(self, files):
       
   172         ret = {}
       
   173         unknown = []
       
   174 
       
   175         for x in files:
       
   176             if x is '.':
       
   177                 return self.map.copy()
       
   178             if x not in self.map:
       
   179                 unknown.append(x)
       
   180             else:
       
   181                 ret[x] = self.map[x]
       
   182 
       
   183         if not unknown:
       
   184             return ret
       
   185 
       
   186         b = self.map.keys()
       
   187         b.sort()
       
   188         blen = len(b)
       
   189 
       
   190         for x in unknown:
       
   191             bs = bisect.bisect(b, x)
       
   192             if bs != 0 and  b[bs-1] == x:
       
   193                 ret[x] = self.map[x]
       
   194                 continue
       
   195             while bs < blen:
       
   196                 s = b[bs]
       
   197                 if len(s) > len(x) and s.startswith(x) and s[len(x)] == '/':
       
   198                     ret[s] = self.map[s]
       
   199                 else:
       
   200                     break
       
   201                 bs += 1
       
   202         return ret
       
   203 
       
   204     def walk(self, files=None, match=util.always, dc=None):
       
   205         self.read()
       
   206 
       
   207         # walk all files by default
       
   208         if not files:
       
   209             files = [self.root]
       
   210             if not dc:
       
   211                 dc = self.map.copy()
       
   212         elif not dc:
       
   213             dc = self.filterfiles(files)
       
   214 
       
   215         known = {'.hg': 1}
       
   216         def seen(fn):
       
   217             if fn in known: return True
       
   218             known[fn] = 1
       
   219         def traverse():
       
   220             for ff in util.unique(files):
       
   221                 f = os.path.join(self.root, ff)
       
   222                 try:
       
   223                     st = os.stat(f)
       
   224                 except OSError, inst:
       
   225                     if ff not in dc: self.ui.warn('%s: %s\n' % (
       
   226                         util.pathto(self.getcwd(), ff),
       
   227                         inst.strerror))
       
   228                     continue
       
   229                 if stat.S_ISDIR(st.st_mode):
       
   230                     for dir, subdirs, fl in os.walk(f):
       
   231                         d = dir[len(self.root) + 1:]
       
   232                         nd = util.normpath(d)
       
   233                         if nd == '.': nd = ''
       
   234                         if seen(nd):
       
   235                             subdirs[:] = []
       
   236                             continue
       
   237                         for sd in subdirs:
       
   238                             ds = os.path.join(nd, sd +'/')
       
   239                             if self.ignore(ds) or not match(ds):
       
   240                                 subdirs.remove(sd)
       
   241                         subdirs.sort()
       
   242                         fl.sort()
       
   243                         for fn in fl:
       
   244                             fn = util.pconvert(os.path.join(d, fn))
       
   245                             yield 'f', fn
       
   246                 elif stat.S_ISREG(st.st_mode):
       
   247                     yield 'f', ff
       
   248                 else:
       
   249                     kind = 'unknown'
       
   250                     if stat.S_ISCHR(st.st_mode): kind = 'character device'
       
   251                     elif stat.S_ISBLK(st.st_mode): kind = 'block device'
       
   252                     elif stat.S_ISFIFO(st.st_mode): kind = 'fifo'
       
   253                     elif stat.S_ISLNK(st.st_mode): kind = 'symbolic link'
       
   254                     elif stat.S_ISSOCK(st.st_mode): kind = 'socket'
       
   255                     self.ui.warn('%s: unsupported file type (type is %s)\n' % (
       
   256                         util.pathto(self.getcwd(), ff),
       
   257                         kind))
       
   258 
       
   259             ks = dc.keys()
       
   260             ks.sort()
       
   261             for k in ks:
       
   262                 yield 'm', k
       
   263 
       
   264         # yield only files that match: all in dirstate, others only if
       
   265         # not in .hgignore
       
   266 
       
   267         for src, fn in util.unique(traverse()):
       
   268             fn = util.normpath(fn)
       
   269             if seen(fn): continue
       
   270             if fn not in dc and self.ignore(fn):
       
   271                 continue
       
   272             if match(fn):
       
   273                 yield src, fn
       
   274 
       
   275     def changes(self, files=None, match=util.always):
       
   276         self.read()
       
   277         if not files:
       
   278             dc = self.map.copy()
       
   279         else:
       
   280             dc = self.filterfiles(files)
       
   281         lookup, modified, added, unknown = [], [], [], []
       
   282         removed, deleted = [], []
       
   283 
       
   284         for src, fn in self.walk(files, match, dc=dc):
       
   285             try:
       
   286                 s = os.stat(os.path.join(self.root, fn))
       
   287             except OSError:
       
   288                 continue
       
   289             if not stat.S_ISREG(s.st_mode):
       
   290                 continue
       
   291             c = dc.get(fn)
       
   292             if c:
       
   293                 del dc[fn]
       
   294                 if c[0] == 'm':
       
   295                     modified.append(fn)
       
   296                 elif c[0] == 'a':
       
   297                     added.append(fn)
       
   298                 elif c[0] == 'r':
       
   299                     unknown.append(fn)
       
   300                 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
       
   301                     modified.append(fn)
       
   302                 elif c[3] != s.st_mtime:
       
   303                     lookup.append(fn)
       
   304             else:
       
   305                 unknown.append(fn)
       
   306 
       
   307         for fn, c in [(fn, c) for fn, c in dc.items() if match(fn)]:
       
   308             if c[0] == 'r':
       
   309                 removed.append(fn)
       
   310             else:
       
   311                 deleted.append(fn)
       
   312         return (lookup, modified, added, removed + deleted, unknown)