/hack/svnutil/sradmin.py
Python | 438 lines | 427 code | 9 blank | 2 comment | 21 complexity | 03acb917badddfac13a40fef4ad73450 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause
- #! /usr/bin/env python
- import os, sys, stat, md5, posixpath, fnmatch
- import pysvn.ra
- from pysvn.delta import Delta
- # ____________________________________________________________
- REPO_URL = 'svn:///home/arigo/svn/arigo/hack/svnutil/repo-sradmin/root'
- TRACKPATHS = ['/etc', '/usr/share/X11/xkb']
- IGNORES = ['*~']
- def get_repo_connx():
- repo = pysvn.ra.connect(REPO_URL)
- return repo, repo.get_latest_rev()
- class NoPermission(OSError):
- pass
- def warn(e):
- if not QUIET and not isinstance(e, NoPermission):
- print >> sys.stderr, '[warning] %s: %s' % (e.__class__.__name__, e)
- def quote(s):
- return "'%s'" % (s.replace("'", "'\\''"),)
- def uidname(uid, _cache={}):
- try:
- return _cache[uid]
- except KeyError:
- import pwd
- try:
- name = pwd.getpwuid(uid)[0]
- except KeyError:
- name = str(uid)
- assert name.isalnum(), (uid, name)
- _cache[uid] = name
- return name
- def gidname(gid, _cache={}):
- try:
- return _cache[gid]
- except KeyError:
- import grp
- try:
- name = grp.getgrgid(gid)[0]
- except KeyError:
- name = str(gid)
- assert name.isalnum(), (gid, name)
- _cache[gid] = name
- return name
- def reprmode(st_mode):
- if stat.S_ISREG(st_mode):
- ckind = '-'
- elif stat.S_ISDIR(st_mode):
- ckind = 'd'
- elif stat.S_ISLNK(st_mode):
- ckind = 'l'
- else:
- ckind = '!'
- letters = [ckind]
- for shift in [6, 3, 0]:
- mode = st_mode >> shift
- if mode & 4: letters.append('r')
- else: letters.append('-')
- if mode & 2: letters.append('w')
- else: letters.append('-')
- if mode & 1: letters.append('x')
- else: letters.append('-')
- return ''.join(letters)
- # ____________________________________________________________
- class FSNode:
- def __init__(self, fspath):
- self.fspath = fspath
- self.st = os.lstat(fspath)
- if stat.S_ISREG(self.st.st_mode):
- self.kind = 'file'
- elif stat.S_ISDIR(self.st.st_mode):
- self.kind = 'dir'
- elif stat.S_ISLNK(self.st.st_mode):
- self.kind = 'symlink'
- else:
- raise OSError("unsupported file kind")
- def listdir(self):
- assert self.kind == 'dir'
- result = {}
- if not os.access(self.fspath, os.R_OK):
- return result
- try:
- names = os.listdir(self.fspath)
- except (IOError, OSError), e:
- warn(e)
- else:
- for name in names:
- try:
- result[name] = FSNode(os.path.join(self.fspath, name))
- except (IOError, OSError), e:
- warn(e)
- return result
- def getdatablocks(self):
- if self.kind == 'file':
- if not os.access(self.fspath, os.R_OK):
- raise NoPermission(self.fspath)
- f = open(self.fspath, 'rb')
- while 1:
- data = f.read(32768)
- if not data: break
- yield data
- f.close()
- elif self.kind == 'symlink':
- yield 'link ' + os.readlink(self.fspath)
- else:
- raise Exception('wrong kind')
- def getchecksum(self):
- m = md5.md5()
- try:
- for block in self.getdatablocks():
- m.update(block)
- except (IOError, OSError), e:
- warn(e)
- return 'unreadable'
- return m.hexdigest()
- def getmetadata(self):
- st = self.st
- return '%s %s %s' % (reprmode(st.st_mode),
- uidname(st.st_uid),
- gidname(st.st_gid))
- def getrakind(self):
- if self.kind == 'dir':
- return 'dir'
- else:
- return 'file'
- def make_commit_delta(self, oldrev):
- delta = Delta(self.fspath.strip('/'), self.getrakind(), oldrev, 'HEAD')
- if oldrev is None:
- if self.kind == 'symlink':
- delta.propdelta['svn:special'] = '*'
- self.delta_update_metadata(delta)
- if self.kind != 'dir':
- self.delta_update_content(delta, basetext='')
- return delta
- def delta_update_metadata(self, delta):
- delta.propdelta['st'] = self.getmetadata()
- def delta_update_content(self, delta, basetext):
- try:
- newtext = ''.join(self.getdatablocks())
- except (IOError, OSError), e:
- warn(e)
- delta.propdelta['unreadable'] = '*'
- else:
- delta.makedelta(basetext, newtext)
- class RANode:
- def __init__(self, connx, rapath, rakind='dir'):
- self.connx = connx
- self.rapath = rapath
- ra, rev = connx
- if rakind == 'dir':
- self.kind = 'dir'
- _, self.props, self.entries = ra.get_dir(self.rapath, rev,
- want_props=True,
- want_contents=True)
- else:
- self.checksum, _, self.props, _ = ra.get_file(self.rapath, rev,
- want_props=True,
- want_contents=False)
- if self.props.get('svn:special'):
- self.kind = 'symlink'
- else:
- self.kind = 'file'
- def listdir(self):
- assert self.kind == 'dir'
- result = {}
- for name, statdict in self.entries.items():
- result[name] = RANode(self.connx,
- posixpath.join(self.rapath, name),
- rakind = statdict['svn:entry:kind'])
- return result
- def getdatablocks(self):
- ra, rev = self.connx
- _, _, _, data = ra.get_file(self.rapath, rev,
- want_props=False,
- want_contents=True)
- return [data]
- def getchecksum(self):
- assert self.kind != 'dir'
- if self.props.get('unreadable'):
- return 'unreadable'
- else:
- return self.checksum
- def getmetadata(self):
- return self.props.get('st', '')
- def getignores(self):
- lines = self.props.get('svn:ignore', '').splitlines()
- return [line for line in lines if line]
- def getrev(self):
- ra, rev = self.connx
- return rev
- def enum_status(path, wc, rn, parentignores=[]):
- c_content = ' '
- c_meta = ' '
- if wc is None:
- c_content = '!'
- elif rn is None:
- for pattern in IGNORES + parentignores:
- if fnmatch.fnmatch(os.path.basename(path), pattern):
- return
- c_content = '?'
- elif wc.kind != rn.kind:
- c_content = '~'
- else:
- if wc.kind == 'dir':
- lst1 = wc.listdir()
- lst2 = rn.listdir()
- names = lst1.copy()
- names.update(lst2)
- names = names.keys()
- names.sort()
- ignores = rn.getignores()
- for name in names:
- for item in enum_status(os.path.join(path, name),
- lst1.get(name),
- lst2.get(name),
- ignores):
- yield item
- else:
- if wc.getchecksum() != rn.getchecksum():
- c_content = 'M'
- if wc.getmetadata() != rn.getmetadata():
- c_meta = 'M'
- if c_content != ' ' or c_meta != ' ':
- yield (c_content, c_meta, path, wc, rn)
- def commit_from_status(callback, logmsg, verbose=False, paths=()):
- connx = get_repo_connx()
- deltas = []
- for trackpath in paths or TRACKPATHS:
- wc = FSNode(trackpath)
- rn = RANode(connx, trackpath.strip('/'))
- for item in enum_status(trackpath, wc, rn):
- changes = False
- for delta in callback(*item):
- #print delta
- deltas.append(delta)
- changes = True
- if verbose:
- c_content, c_meta, path, _, _ = item
- if c_content != ' ' or c_meta != ' ' or changes:
- if changes:
- color = color_bold
- else:
- color = ''
- print '%s%c%c\t%s%s' % (color, c_content, c_meta, path,
- color_stop)
- commit(connx, deltas, logmsg)
- color_bold = '\033[1m'
- color_red = '\033[31m\033[1m'
- color_stop = '\033[0m'
- # ____________________________________________________________
- def commit(connx, deltas, logmsg):
- if deltas:
- answer = raw_input('sradmin: commit %d changes? ' % (len(deltas),))
- if not answer.lower().startswith('y'):
- return
- ra, rev = connx
- newrev, _ = ra.commit(LOGMSG or logmsg, deltas, {'': rev})
- print 'sradmin: committed revision %d.' % (newrev,)
- def cmd_status(*paths):
- connx = get_repo_connx()
- for trackpath in paths or TRACKPATHS:
- wc = FSNode(trackpath)
- rn = RANode(connx, trackpath.strip('/'))
- for c_content, c_meta, path, _, _ in enum_status(trackpath, wc, rn):
- print '%c%c\t%s' % (c_content, c_meta, path)
- cmd_st = cmd_status
- def cmd_ignore(*paths):
- for path1 in paths:
- for trackpath in TRACKPATHS:
- if os.path.dirname(path1).startswith(trackpath):
- break
- else:
- raise Exception("path not tracked: %r" % (path1,))
- connx = get_repo_connx()
- ra, rev = connx
- deltas = []
- for path1 in paths:
- dir1 = os.path.dirname(path1)
- name1 = os.path.basename(path1)
- rn = RANode(connx, dir1.strip('/'))
- if name1 not in rn.getignores():
- svnignore = rn.props.get('svn:ignore', '')
- if not svnignore.endswith('\n'):
- svnignore += '\n'
- svnignore += name1
- svnignore += '\n'
- delta = Delta(rn.rapath, 'dir', rev, 'HEAD')
- delta.propdelta['svn:ignore'] = svnignore
- #print delta
- deltas.append(delta)
- kind = ra.check_path(path1.strip('/'))
- if kind:
- delta = Delta(path1.strip('/'), kind, rev, None) # svn rm
- deltas.append(delta)
- commit(connx, deltas, "cmd_ignore")
- def _addall(c_content, c_meta, path, wc, rn):
- if c_content == '?':
- yield wc.make_commit_delta(oldrev=None)
- def cmd_addall():
- commit_from_status(_addall, "cmd_addall", verbose=True)
- def _rmall(c_content, c_meta, path, wc, rn):
- if c_content == '!':
- ra, oldrev = rn.connx
- yield Delta(path.strip('/'), rn.kind, oldrev, None)
- def cmd_rmall():
- commit_from_status(_rmall, "cmd_rmall", verbose=True)
- def _commit(c_content, c_meta, path, wc, rn):
- if c_content == 'M' or c_meta == 'M':
- delta = Delta(path.strip('/'), wc.getrakind(), rn.getrev(), 'HEAD')
- if c_meta == 'M':
- wc.delta_update_metadata(delta)
- if c_content == 'M':
- basetext = ''.join(rn.getdatablocks())
- wc.delta_update_content(delta, basetext)
- del basetext
- yield delta
- def cmd_commit(*paths):
- commit_from_status(_commit, "cmd_commit", verbose=True, paths=paths)
- def _commitall(c_content, c_meta, path, wc, rn):
- for op in [_addall, _rmall, _commit]:
- for delta in op(c_content, c_meta, path, wc, rn):
- yield delta
- def cmd_commitall():
- commit_from_status(_commitall, "cmd_commitall", verbose=True)
- def cmd_create():
- connx = get_repo_connx()
- ra, rev = connx
- deltas = []
- for trackpath in TRACKPATHS:
- check = ''
- for component in trackpath.split('/'):
- if component:
- check = posixpath.join(check, component)
- if ra.check_path(check) != 'dir':
- delta = Delta(check, 'dir', None, 'HEAD')
- deltas.append(delta)
- print 'A %s' % (check,)
- commit(connx, deltas, "cmd_create")
- def cmd_diff(*paths):
- seen = []
- def diff(c_content, c_meta, path, wc, rn):
- if c_content != ' ' or c_meta != ' ':
- if not seen:
- print '='*79
- print 'Index:', path
- print
- if c_meta != ' ':
- print 'PERMISSIONS CHANGED'
- print '-\t' + rn.getmetadata()
- print '+\t' + wc.getmetadata()
- print
- if c_content == ' ':
- pass
- elif c_content == '!':
- print 'REMOVED'
- print
- elif c_content == '?':
- print 'ADDED'
- print
- elif c_content == 'M':
- if not QUIET:
- tmpfile = '/tmp/sradmin-repository'
- f = open(tmpfile, 'w')
- f.writelines(rn.getdatablocks())
- f.close()
- sys.stdout.flush()
- os.system("diff -u %s %s" % (quote(tmpfile),
- quote(wc.fspath)))
- os.unlink(tmpfile)
- else:
- assert False, repr(c_content)
- print '='*79
- seen.append(path)
- return []
- commit_from_status(diff, None, paths=paths)
- QUIET = False
- LOGMSG = None
- if __name__ == '__main__':
- while sys.argv[1].startswith('-'):
- opt = sys.argv.pop(1)
- if opt == '-q':
- QUIET = True
- elif opt == '-m':
- LOGMSG = sys.argv.pop(1)
- else:
- raise Exception("unknown option: %r" % (opt,))
- cmd = sys.argv[1]
- globals()['cmd_' + cmd](*sys.argv[2:])