You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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