PageRenderTime 49ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/hack/svnutil/sradmin.py

https://bitbucket.org/arigo/arigo/
Python | 438 lines | 427 code | 9 blank | 2 comment | 21 complexity | 03acb917badddfac13a40fef4ad73450 MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause
  1. #! /usr/bin/env python
  2. import os, sys, stat, md5, posixpath, fnmatch
  3. import pysvn.ra
  4. from pysvn.delta import Delta
  5. # ____________________________________________________________
  6. REPO_URL = 'svn:///home/arigo/svn/arigo/hack/svnutil/repo-sradmin/root'
  7. TRACKPATHS = ['/etc', '/usr/share/X11/xkb']
  8. IGNORES = ['*~']
  9. def get_repo_connx():
  10. repo = pysvn.ra.connect(REPO_URL)
  11. return repo, repo.get_latest_rev()
  12. class NoPermission(OSError):
  13. pass
  14. def warn(e):
  15. if not QUIET and not isinstance(e, NoPermission):
  16. print >> sys.stderr, '[warning] %s: %s' % (e.__class__.__name__, e)
  17. def quote(s):
  18. return "'%s'" % (s.replace("'", "'\\''"),)
  19. def uidname(uid, _cache={}):
  20. try:
  21. return _cache[uid]
  22. except KeyError:
  23. import pwd
  24. try:
  25. name = pwd.getpwuid(uid)[0]
  26. except KeyError:
  27. name = str(uid)
  28. assert name.isalnum(), (uid, name)
  29. _cache[uid] = name
  30. return name
  31. def gidname(gid, _cache={}):
  32. try:
  33. return _cache[gid]
  34. except KeyError:
  35. import grp
  36. try:
  37. name = grp.getgrgid(gid)[0]
  38. except KeyError:
  39. name = str(gid)
  40. assert name.isalnum(), (gid, name)
  41. _cache[gid] = name
  42. return name
  43. def reprmode(st_mode):
  44. if stat.S_ISREG(st_mode):
  45. ckind = '-'
  46. elif stat.S_ISDIR(st_mode):
  47. ckind = 'd'
  48. elif stat.S_ISLNK(st_mode):
  49. ckind = 'l'
  50. else:
  51. ckind = '!'
  52. letters = [ckind]
  53. for shift in [6, 3, 0]:
  54. mode = st_mode >> shift
  55. if mode & 4: letters.append('r')
  56. else: letters.append('-')
  57. if mode & 2: letters.append('w')
  58. else: letters.append('-')
  59. if mode & 1: letters.append('x')
  60. else: letters.append('-')
  61. return ''.join(letters)
  62. # ____________________________________________________________
  63. class FSNode:
  64. def __init__(self, fspath):
  65. self.fspath = fspath
  66. self.st = os.lstat(fspath)
  67. if stat.S_ISREG(self.st.st_mode):
  68. self.kind = 'file'
  69. elif stat.S_ISDIR(self.st.st_mode):
  70. self.kind = 'dir'
  71. elif stat.S_ISLNK(self.st.st_mode):
  72. self.kind = 'symlink'
  73. else:
  74. raise OSError("unsupported file kind")
  75. def listdir(self):
  76. assert self.kind == 'dir'
  77. result = {}
  78. if not os.access(self.fspath, os.R_OK):
  79. return result
  80. try:
  81. names = os.listdir(self.fspath)
  82. except (IOError, OSError), e:
  83. warn(e)
  84. else:
  85. for name in names:
  86. try:
  87. result[name] = FSNode(os.path.join(self.fspath, name))
  88. except (IOError, OSError), e:
  89. warn(e)
  90. return result
  91. def getdatablocks(self):
  92. if self.kind == 'file':
  93. if not os.access(self.fspath, os.R_OK):
  94. raise NoPermission(self.fspath)
  95. f = open(self.fspath, 'rb')
  96. while 1:
  97. data = f.read(32768)
  98. if not data: break
  99. yield data
  100. f.close()
  101. elif self.kind == 'symlink':
  102. yield 'link ' + os.readlink(self.fspath)
  103. else:
  104. raise Exception('wrong kind')
  105. def getchecksum(self):
  106. m = md5.md5()
  107. try:
  108. for block in self.getdatablocks():
  109. m.update(block)
  110. except (IOError, OSError), e:
  111. warn(e)
  112. return 'unreadable'
  113. return m.hexdigest()
  114. def getmetadata(self):
  115. st = self.st
  116. return '%s %s %s' % (reprmode(st.st_mode),
  117. uidname(st.st_uid),
  118. gidname(st.st_gid))
  119. def getrakind(self):
  120. if self.kind == 'dir':
  121. return 'dir'
  122. else:
  123. return 'file'
  124. def make_commit_delta(self, oldrev):
  125. delta = Delta(self.fspath.strip('/'), self.getrakind(), oldrev, 'HEAD')
  126. if oldrev is None:
  127. if self.kind == 'symlink':
  128. delta.propdelta['svn:special'] = '*'
  129. self.delta_update_metadata(delta)
  130. if self.kind != 'dir':
  131. self.delta_update_content(delta, basetext='')
  132. return delta
  133. def delta_update_metadata(self, delta):
  134. delta.propdelta['st'] = self.getmetadata()
  135. def delta_update_content(self, delta, basetext):
  136. try:
  137. newtext = ''.join(self.getdatablocks())
  138. except (IOError, OSError), e:
  139. warn(e)
  140. delta.propdelta['unreadable'] = '*'
  141. else:
  142. delta.makedelta(basetext, newtext)
  143. class RANode:
  144. def __init__(self, connx, rapath, rakind='dir'):
  145. self.connx = connx
  146. self.rapath = rapath
  147. ra, rev = connx
  148. if rakind == 'dir':
  149. self.kind = 'dir'
  150. _, self.props, self.entries = ra.get_dir(self.rapath, rev,
  151. want_props=True,
  152. want_contents=True)
  153. else:
  154. self.checksum, _, self.props, _ = ra.get_file(self.rapath, rev,
  155. want_props=True,
  156. want_contents=False)
  157. if self.props.get('svn:special'):
  158. self.kind = 'symlink'
  159. else:
  160. self.kind = 'file'
  161. def listdir(self):
  162. assert self.kind == 'dir'
  163. result = {}
  164. for name, statdict in self.entries.items():
  165. result[name] = RANode(self.connx,
  166. posixpath.join(self.rapath, name),
  167. rakind = statdict['svn:entry:kind'])
  168. return result
  169. def getdatablocks(self):
  170. ra, rev = self.connx
  171. _, _, _, data = ra.get_file(self.rapath, rev,
  172. want_props=False,
  173. want_contents=True)
  174. return [data]
  175. def getchecksum(self):
  176. assert self.kind != 'dir'
  177. if self.props.get('unreadable'):
  178. return 'unreadable'
  179. else:
  180. return self.checksum
  181. def getmetadata(self):
  182. return self.props.get('st', '')
  183. def getignores(self):
  184. lines = self.props.get('svn:ignore', '').splitlines()
  185. return [line for line in lines if line]
  186. def getrev(self):
  187. ra, rev = self.connx
  188. return rev
  189. def enum_status(path, wc, rn, parentignores=[]):
  190. c_content = ' '
  191. c_meta = ' '
  192. if wc is None:
  193. c_content = '!'
  194. elif rn is None:
  195. for pattern in IGNORES + parentignores:
  196. if fnmatch.fnmatch(os.path.basename(path), pattern):
  197. return
  198. c_content = '?'
  199. elif wc.kind != rn.kind:
  200. c_content = '~'
  201. else:
  202. if wc.kind == 'dir':
  203. lst1 = wc.listdir()
  204. lst2 = rn.listdir()
  205. names = lst1.copy()
  206. names.update(lst2)
  207. names = names.keys()
  208. names.sort()
  209. ignores = rn.getignores()
  210. for name in names:
  211. for item in enum_status(os.path.join(path, name),
  212. lst1.get(name),
  213. lst2.get(name),
  214. ignores):
  215. yield item
  216. else:
  217. if wc.getchecksum() != rn.getchecksum():
  218. c_content = 'M'
  219. if wc.getmetadata() != rn.getmetadata():
  220. c_meta = 'M'
  221. if c_content != ' ' or c_meta != ' ':
  222. yield (c_content, c_meta, path, wc, rn)
  223. def commit_from_status(callback, logmsg, verbose=False, paths=()):
  224. connx = get_repo_connx()
  225. deltas = []
  226. for trackpath in paths or TRACKPATHS:
  227. wc = FSNode(trackpath)
  228. rn = RANode(connx, trackpath.strip('/'))
  229. for item in enum_status(trackpath, wc, rn):
  230. changes = False
  231. for delta in callback(*item):
  232. #print delta
  233. deltas.append(delta)
  234. changes = True
  235. if verbose:
  236. c_content, c_meta, path, _, _ = item
  237. if c_content != ' ' or c_meta != ' ' or changes:
  238. if changes:
  239. color = color_bold
  240. else:
  241. color = ''
  242. print '%s%c%c\t%s%s' % (color, c_content, c_meta, path,
  243. color_stop)
  244. commit(connx, deltas, logmsg)
  245. color_bold = '\033[1m'
  246. color_red = '\033[31m\033[1m'
  247. color_stop = '\033[0m'
  248. # ____________________________________________________________
  249. def commit(connx, deltas, logmsg):
  250. if deltas:
  251. answer = raw_input('sradmin: commit %d changes? ' % (len(deltas),))
  252. if not answer.lower().startswith('y'):
  253. return
  254. ra, rev = connx
  255. newrev, _ = ra.commit(LOGMSG or logmsg, deltas, {'': rev})
  256. print 'sradmin: committed revision %d.' % (newrev,)
  257. def cmd_status(*paths):
  258. connx = get_repo_connx()
  259. for trackpath in paths or TRACKPATHS:
  260. wc = FSNode(trackpath)
  261. rn = RANode(connx, trackpath.strip('/'))
  262. for c_content, c_meta, path, _, _ in enum_status(trackpath, wc, rn):
  263. print '%c%c\t%s' % (c_content, c_meta, path)
  264. cmd_st = cmd_status
  265. def cmd_ignore(*paths):
  266. for path1 in paths:
  267. for trackpath in TRACKPATHS:
  268. if os.path.dirname(path1).startswith(trackpath):
  269. break
  270. else:
  271. raise Exception("path not tracked: %r" % (path1,))
  272. connx = get_repo_connx()
  273. ra, rev = connx
  274. deltas = []
  275. for path1 in paths:
  276. dir1 = os.path.dirname(path1)
  277. name1 = os.path.basename(path1)
  278. rn = RANode(connx, dir1.strip('/'))
  279. if name1 not in rn.getignores():
  280. svnignore = rn.props.get('svn:ignore', '')
  281. if not svnignore.endswith('\n'):
  282. svnignore += '\n'
  283. svnignore += name1
  284. svnignore += '\n'
  285. delta = Delta(rn.rapath, 'dir', rev, 'HEAD')
  286. delta.propdelta['svn:ignore'] = svnignore
  287. #print delta
  288. deltas.append(delta)
  289. kind = ra.check_path(path1.strip('/'))
  290. if kind:
  291. delta = Delta(path1.strip('/'), kind, rev, None) # svn rm
  292. deltas.append(delta)
  293. commit(connx, deltas, "cmd_ignore")
  294. def _addall(c_content, c_meta, path, wc, rn):
  295. if c_content == '?':
  296. yield wc.make_commit_delta(oldrev=None)
  297. def cmd_addall():
  298. commit_from_status(_addall, "cmd_addall", verbose=True)
  299. def _rmall(c_content, c_meta, path, wc, rn):
  300. if c_content == '!':
  301. ra, oldrev = rn.connx
  302. yield Delta(path.strip('/'), rn.kind, oldrev, None)
  303. def cmd_rmall():
  304. commit_from_status(_rmall, "cmd_rmall", verbose=True)
  305. def _commit(c_content, c_meta, path, wc, rn):
  306. if c_content == 'M' or c_meta == 'M':
  307. delta = Delta(path.strip('/'), wc.getrakind(), rn.getrev(), 'HEAD')
  308. if c_meta == 'M':
  309. wc.delta_update_metadata(delta)
  310. if c_content == 'M':
  311. basetext = ''.join(rn.getdatablocks())
  312. wc.delta_update_content(delta, basetext)
  313. del basetext
  314. yield delta
  315. def cmd_commit(*paths):
  316. commit_from_status(_commit, "cmd_commit", verbose=True, paths=paths)
  317. def _commitall(c_content, c_meta, path, wc, rn):
  318. for op in [_addall, _rmall, _commit]:
  319. for delta in op(c_content, c_meta, path, wc, rn):
  320. yield delta
  321. def cmd_commitall():
  322. commit_from_status(_commitall, "cmd_commitall", verbose=True)
  323. def cmd_create():
  324. connx = get_repo_connx()
  325. ra, rev = connx
  326. deltas = []
  327. for trackpath in TRACKPATHS:
  328. check = ''
  329. for component in trackpath.split('/'):
  330. if component:
  331. check = posixpath.join(check, component)
  332. if ra.check_path(check) != 'dir':
  333. delta = Delta(check, 'dir', None, 'HEAD')
  334. deltas.append(delta)
  335. print 'A %s' % (check,)
  336. commit(connx, deltas, "cmd_create")
  337. def cmd_diff(*paths):
  338. seen = []
  339. def diff(c_content, c_meta, path, wc, rn):
  340. if c_content != ' ' or c_meta != ' ':
  341. if not seen:
  342. print '='*79
  343. print 'Index:', path
  344. print
  345. if c_meta != ' ':
  346. print 'PERMISSIONS CHANGED'
  347. print '-\t' + rn.getmetadata()
  348. print '+\t' + wc.getmetadata()
  349. print
  350. if c_content == ' ':
  351. pass
  352. elif c_content == '!':
  353. print 'REMOVED'
  354. print
  355. elif c_content == '?':
  356. print 'ADDED'
  357. print
  358. elif c_content == 'M':
  359. if not QUIET:
  360. tmpfile = '/tmp/sradmin-repository'
  361. f = open(tmpfile, 'w')
  362. f.writelines(rn.getdatablocks())
  363. f.close()
  364. sys.stdout.flush()
  365. os.system("diff -u %s %s" % (quote(tmpfile),
  366. quote(wc.fspath)))
  367. os.unlink(tmpfile)
  368. else:
  369. assert False, repr(c_content)
  370. print '='*79
  371. seen.append(path)
  372. return []
  373. commit_from_status(diff, None, paths=paths)
  374. QUIET = False
  375. LOGMSG = None
  376. if __name__ == '__main__':
  377. while sys.argv[1].startswith('-'):
  378. opt = sys.argv.pop(1)
  379. if opt == '-q':
  380. QUIET = True
  381. elif opt == '-m':
  382. LOGMSG = sys.argv.pop(1)
  383. else:
  384. raise Exception("unknown option: %r" % (opt,))
  385. cmd = sys.argv[1]
  386. globals()['cmd_' + cmd](*sys.argv[2:])