您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
14 年前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. """
  2. Version Control:
  3. Schema:
  4. properties (key, value)
  5. uncommitted (fname, ftype, content, timestamp)
  6. files (fname, ftype, content, timestamp, version)
  7. log (fname, ftype, version)
  8. bundle_files (fname primary key)
  9. Discussion:
  10. There are 2 databases, versions.db and versions-local.db
  11. All changes are commited to versions-local.db, when the patches are complete, the developer
  12. must pull the latest .wnf db and merge
  13. versions-local.db is never commited in the global repository
  14. TODO
  15. - walk
  16. - merge
  17. - client
  18. """
  19. import unittest
  20. import os
  21. test_file = {'fname':'test.js', 'ftype':'js', 'content':'test_code', 'timestamp':'1100'}
  22. root_path = os.path.abspath(os.curdir)
  23. def edit_file():
  24. # edit a file
  25. p = os.path.join(root_path, 'lib/js/core.js')
  26. # read
  27. f1 = open(p, 'r')
  28. content = f1.read()
  29. f1.close()
  30. # write
  31. f = open(p, 'w')
  32. f.write(content)
  33. f.close()
  34. return os.path.relpath(p, root_path)
  35. verbose = False
  36. class TestVC(unittest.TestCase):
  37. def setUp(self):
  38. self.vc = VersionControl()
  39. self.vc.repo.setup()
  40. def test_add(self):
  41. self.vc.repo.add(**test_file)
  42. ret = self.vc.repo.sql('select * from uncommitted', as_dict=1)[0]
  43. self.assertTrue(ret==test_file)
  44. def test_commit(self):
  45. last_number = self.vc.repo.get_value('last_version_number')
  46. self.vc.repo.add(**test_file)
  47. self.vc.repo.commit()
  48. # test version
  49. number = self.vc.repo.get_value('last_version_number')
  50. version = self.vc.repo.sql("select version from versions where number=?", (number,))[0][0]
  51. self.assertTrue(number != last_number)
  52. # test file
  53. self.assertTrue(self.vc.repo.get_file('test.js')['content'] == test_file['content'])
  54. # test uncommitted
  55. self.assertFalse(self.vc.repo.sql("select * from uncommitted"))
  56. # test log
  57. self.assertTrue(self.vc.repo.sql("select * from log where version=?", (version,)))
  58. def test_diff(self):
  59. self.vc.repo.add(**test_file)
  60. self.vc.repo.commit()
  61. self.assertTrue(self.vc.repo.diff(None), ['test.js'])
  62. def test_walk(self):
  63. # add
  64. self.vc.add_all()
  65. # check if added
  66. ret = self.vc.repo.sql("select * from uncommitted", as_dict=1)
  67. self.assertTrue(len(ret)>0)
  68. self.vc.repo.commit()
  69. p = edit_file()
  70. # add
  71. self.vc.add_all()
  72. # check if added
  73. ret = self.vc.repo.sql("select * from uncommitted", as_dict=1)
  74. self.assertTrue(p in [r['fname'] for r in ret])
  75. def test_merge(self):
  76. self.vc.add_all()
  77. self.vc.repo.commit()
  78. # write the file
  79. self.vc.repo.conn.commit()
  80. # make master (copy)
  81. os.system('cp %s %s' % (os.path.join(root_path, 'versions-local.db'), os.path.join(root_path, 'versions-master.db')))
  82. self.vc.setup_master()
  83. p = edit_file()
  84. self.vc.add_all()
  85. self.vc.repo.commit()
  86. self.vc.merge(self.vc.repo, self.vc.master)
  87. log = self.vc.master.diff(int(self.vc.master.get_value('last_version_number'))-1)
  88. self.assertTrue(p in log)
  89. def tearDown(self):
  90. self.vc.close()
  91. os.remove(self.vc.repo.db_path)
  92. class VersionControl:
  93. def __init__(self, root=None):
  94. #self.master = Repository(self, 'versions-master.db')
  95. self.set_root(root)
  96. self.repo = Repository(self, 'versions-local.db')
  97. self.ignore_folders = ['.git', '.', '..']
  98. self.ignore_files = ['py', 'pyc', 'DS_Store', 'txt', 'db-journal', 'db']
  99. def setup_master(self):
  100. self.master = Repository(self, 'versions-master.db')
  101. def set_root(self, path=None):
  102. """
  103. set / reset root and connect
  104. (the root path is the path of the folder)
  105. """
  106. if not path:
  107. raise Exception, 'path must be given'
  108. self.root_path = path
  109. def timestamp(self, path):
  110. """
  111. returns timestamp
  112. """
  113. import os
  114. return int(os.stat(path).st_mtime)
  115. def add_all(self):
  116. """
  117. walk the root folder Add all dirty files to the vcs
  118. """
  119. import os
  120. for wt in os.walk(self.root_path, followlinks = True):
  121. # ignore folders
  122. for folder in self.ignore_folders:
  123. if folder in wt[1]:
  124. wt[1].remove(folder)
  125. for fname in wt[2]:
  126. fpath = os.path.join(wt[0], fname)
  127. if fname.endswith('build.json'):
  128. self.repo.add_bundle(fpath)
  129. continue
  130. if fname.split('.')[-1] in self.ignore_files:
  131. # nothing to do
  132. continue
  133. # file does not exist
  134. if not self.repo.exists(fpath):
  135. if verbose:
  136. print "%s added" % fpath
  137. self.repo.add(fpath)
  138. # file changed
  139. else:
  140. if self.timestamp(fpath) != self.repo.timestamp(fpath):
  141. if verbose:
  142. print "%s changed" % fpath
  143. self.repo.add(fpath)
  144. def version_diff(self, source, target):
  145. """
  146. get missing versions in target
  147. """
  148. # find versions in source not in target
  149. d = []
  150. versions = source.sql("select version from versions")
  151. for v in versions:
  152. if not target.sql("select version from versions where version=?", v):
  153. d.append(v)
  154. return d
  155. def merge(self, source, target):
  156. """
  157. merges with two repositories
  158. """
  159. for d in self.version_diff(source, target):
  160. for f in source.sql("select * from files where version=?", d, as_dict=1):
  161. target.add(**f)
  162. target.commit(d[0])
  163. def close(self):
  164. self.repo.conn.commit()
  165. self.repo.conn.close()
  166. class Repository:
  167. def __init__(self, vc, fname = 'versions-local.db'):
  168. self.vc = vc
  169. import sqlite3
  170. self.db_path = os.path.join(self.vc.root_path, fname)
  171. self.conn = sqlite3.connect(self.db_path)
  172. self.cur = self.conn.cursor()
  173. def setup(self):
  174. """
  175. setup the schema
  176. """
  177. print "setting up %s..." % self.db_path
  178. self.cur.executescript("""
  179. create table properties(pkey primary key, value);
  180. create table uncommitted(fname primary key, ftype, content, timestamp);
  181. create table files (fname primary key, ftype, content, timestamp, version);
  182. create table log (fname, ftype, version);
  183. create table versions (number integer primary key, version);
  184. create table bundles(fname primary key);
  185. """)
  186. def sql(self, query, values=(), as_dict=None):
  187. """
  188. like webnotes.db.sql
  189. """
  190. self.cur.execute(query, values)
  191. res = self.cur.fetchall()
  192. if as_dict:
  193. out = []
  194. for row in res:
  195. d = {}
  196. for idx, col in enumerate(self.cur.description):
  197. d[col[0]] = row[idx]
  198. out.append(d)
  199. return out
  200. return res
  201. def get_value(self, key):
  202. """
  203. returns value of a property
  204. """
  205. ret = self.sql("select `value` from properties where `pkey`=?", (key,))
  206. return ret and ret[0][0] or None
  207. def set_value(self, key, value):
  208. """
  209. returns value of a property
  210. """
  211. self.sql("insert or replace into properties(pkey, value) values (?, ?)", (key,value))
  212. def add(self, fname, ftype=None, timestamp=None, content=None, version=None):
  213. """
  214. add to uncommitted
  215. """
  216. import os
  217. # commit relative path
  218. fname = os.path.relpath(fname, self.vc.root_path)
  219. if not ftype:
  220. ftype = fname.split('.')[-1]
  221. if not timestamp:
  222. timestamp = self.vc.timestamp(fname)
  223. self.sql("insert or replace into uncommitted(fname, ftype, timestamp, content) values (?, ?, ?, ?)" \
  224. , (fname, ftype, timestamp, content))
  225. def new_version(self):
  226. """
  227. return a random version id
  228. """
  229. import random
  230. # genarate id (global)
  231. return '%016x' % random.getrandbits(64)
  232. def update_number(self, version):
  233. """
  234. update version.number
  235. """
  236. # set number (local)
  237. self.sql("insert into versions (number, version) values (null, ?)", (version,))
  238. number = self.sql("select last_insert_rowid()")[0][0]
  239. self.set_value('last_version_number', number)
  240. def commit(self, version=None):
  241. """
  242. rebuild bundles if necessary
  243. copy uncommitted files to repository, update the log and add the change
  244. """
  245. # make bundles
  246. from bundle import Bundle
  247. Bundle().bundle(self.vc)
  248. # get a new version number
  249. if not version: version = self.new_version()
  250. self.update_number(version)
  251. # find added files to commit
  252. self.add_from_uncommitted(version)
  253. # clear uncommitted
  254. self.sql("delete from uncommitted")
  255. def add_from_uncommitted(self, version):
  256. """
  257. move files from uncommitted table to files table
  258. """
  259. added = self.sql("select * from uncommitted", as_dict=1)
  260. for f in added:
  261. # move them to "files"
  262. self.sql("""
  263. insert or replace into files
  264. (fname, ftype, timestamp, content, version)
  265. values (?,?,?,?,?)
  266. """, (f['fname'], f['ftype'], f['timestamp'], f['content'], version))
  267. # update log
  268. self.add_log(f['fname'], f['ftype'], version)
  269. def exists(self, fname):
  270. """
  271. true if exists
  272. """
  273. fname = os.path.relpath(fname, self.vc.root_path)
  274. return self.sql("select fname from files where fname=?", (fname,))
  275. def timestamp(self, fname):
  276. """
  277. get timestamp
  278. """
  279. fname = os.path.relpath(fname, self.vc.root_path)
  280. return int(self.sql("select timestamp from files where fname=?", (fname,))[0][0] or 0)
  281. def diff(self, number):
  282. """
  283. get changed files since number
  284. """
  285. if number is None: number = 0
  286. ret = self.sql("""
  287. select log.fname from log, versions
  288. where versions.number > ?
  289. and versions.version = log.version""", (number,))
  290. return list(set([f[0] for f in ret]))
  291. def uncommitted(self):
  292. """
  293. return list of uncommitted files
  294. """
  295. return [f[0] for f in self.sql("select fname from uncommitted")]
  296. def get_file(self, fname):
  297. """
  298. return file info as dict
  299. """
  300. return self.sql("select * from files where fname=?", (fname,), as_dict=1)[0]
  301. def add_log(self, fname, ftype, version):
  302. """
  303. add file to log
  304. """
  305. self.sql("insert into log(fname, ftype, version) values (?,?,?)", (fname, ftype, version))
  306. def add_bundle(self, fname):
  307. """
  308. add to bundles
  309. """
  310. self.sql("insert or replace into bundles(fname) values (?)", (fname,))
  311. if __name__=='__main__':
  312. import os, sys
  313. sys.path.append('py')
  314. sys.path.append('lib/py')
  315. unittest.main()