From 04cb1fe38e5bea62b7ed31956e5548f58fb79689 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=C5=81ukasz=20Rekucki?= Date: Tue, 22 Sep 2009 13:50:46 +0200 Subject: [PATCH] Fixed several DC/split related bugs. No autocopy of DC for now. --- apps/explorer/forms.py | 2 +- apps/explorer/views.py | 15 +- lib/wlrepo/__init__.py | 16 +- lib/wlrepo/backend_mercurial.py | 257 ++++++++++++++++---------- lib/wlrepo/tests/test_mercurial.py | 162 ++++++++++++++-- project/static/js/editor.ui.js | 4 +- project/templates/explorer/split.html | 2 +- 7 files changed, 334 insertions(+), 124 deletions(-) diff --git a/apps/explorer/forms.py b/apps/explorer/forms.py index fffdeb87..6c5c88d5 100644 --- a/apps/explorer/forms.py +++ b/apps/explorer/forms.py @@ -95,7 +95,7 @@ class DublinCoreForm(forms.Form): source_name = forms.CharField(widget=forms.Textarea, required=False) source_url = forms.URLField(verify_exists=False, required=False) url = forms.URLField(verify_exists=False) - parts = forms.CharField(widget=forms.Textarea, required=False) + parts = ListField(required=False) license = forms.CharField(required=False) license_description = forms.CharField(widget=forms.Textarea, required=False) diff --git a/apps/explorer/views.py b/apps/explorer/views.py index ef2a3962..889fda5c 100644 --- a/apps/explorer/views.py +++ b/apps/explorer/views.py @@ -476,11 +476,12 @@ def split_text(request, path): print "validating sform" if sform.is_valid(): valid = True - if sform.cleaned_data['autoxml']: - print "validating dcform" - valid = dcform.is_valid() +# if sform.cleaned_data['autoxml']: +# print "validating dcform" +# valid = dcform.is_valid() print "valid is ", valid + if valid: uri = path + '$' + sform.cleaned_data['partname'] child_rpath = file_path(uri) @@ -496,7 +497,7 @@ def split_text(request, path): fulltext = sform.cleaned_data['fulltext'] fulltext = fulltext.replace(u'', - librarian.xinclude_forURI('wlrepo://'+uri) ) + librarian.xinclude_forURI(u'wlrepo://'+uri) ) repo._write_file(rpath, fulltext.encode('utf-8')) @@ -504,7 +505,7 @@ def split_text(request, path): if sform.cleaned_data['autoxml']: # this is a horrible hack - really bi = dcparser.BookInfo.from_element(librarian.DEFAULT_BOOKINFO.to_etree()) - bi.update(dcform.cleaned_data) + bi.update(dcform.data) newtext = librarian.wrap_text(newtext, \ unicode(date.today()), bookinfo=bi ) @@ -516,8 +517,10 @@ def split_text(request, path): if repo.in_branch(split_action, file_branch(path, request.user)): # redirect to success + import urllib + uri = urllib.quote( unicode(uri).encode('utf-8')) return HttpResponseRedirect( reverse('split-success',\ - kwargs={'path': path})+'?child='+uri) + kwargs={'path': path})+'?child='+uri ) else: try: # to read the current DC repo = hg.Repository(settings.REPOSITORY_PATH) diff --git a/lib/wlrepo/__init__.py b/lib/wlrepo/__init__.py index 606f2d45..210322d0 100644 --- a/lib/wlrepo/__init__.py +++ b/lib/wlrepo/__init__.py @@ -38,12 +38,20 @@ class Cabinet(object): if name: self._name = name self._maindoc = '' + self._user = self._document = None elif doc and user: + self._user = user + self._document = doc self._name = user + ':' + doc self._maindoc = doc else: raise ValueError("You must provide either name or doc and user.") + print "new cab:", self._name, self._user, self._document + + @property + def username(self): + return self._user def __str__(self): return "Cabinet(%s)" % self._name @@ -64,7 +72,8 @@ class Cabinet(object): def create(self, name, initial_data=''): """Create a new sub-document in the cabinet with the given name.""" pass - + + @property def maindoc_name(self): return self._maindoc @@ -116,9 +125,8 @@ class Document(object): class Shelf(object): - def __init__(self, cabinet): - self._cabinet = cabinet - + def __init__(self, lib): + self._library = lib # # Exception classes diff --git a/lib/wlrepo/backend_mercurial.py b/lib/wlrepo/backend_mercurial.py index 11ecfc79..94bf52a5 100644 --- a/lib/wlrepo/backend_mercurial.py +++ b/lib/wlrepo/backend_mercurial.py @@ -8,7 +8,7 @@ __doc__ = """RAL implementation over Mercurial""" import mercurial from mercurial import localrepo as hglrepo from mercurial import ui as hgui -from mercurial.node import hex as to_hex +from mercurial.node import nullid import re import wlrepo @@ -73,14 +73,14 @@ class MercurialLibrary(wlrepo.Library): lock = self._lock(True) try: if self._has_branch(bname): - return MercurialCabinet(self, bname, doc=docid, user=user) + return MercurialCabinet(self, doc=docid, user=user) if not create: raise wlrepo.CabinetNotFound(bname) # check if the docid exists in the main cabinet needs_touch = not self._maincab.exists(docid) - cab = MercurialCabinet(self, bname, doc=docid, user=user) + cab = MercurialCabinet(self, doc=docid, user=user) name, fileid = cab._filename(None) @@ -94,7 +94,7 @@ class MercurialLibrary(wlrepo.Library): # create the branch self._create_branch(bname, before_commit=cleanup_action) - return MercurialCabinet(self, bname, doc=docid, user=user) + return MercurialCabinet(self, doc=docid, user=user) finally: lock.release() @@ -212,10 +212,8 @@ class MercurialLibrary(wlrepo.Library): self._checkout(self._branch_tip(branchname)) return branchname - # - # Merges - # - + def shelf(self, nodeid): + return MercurialShelf(self, self._changectx(nodeid)) # @@ -230,19 +228,18 @@ class MercurialLibrary(wlrepo.Library): class MercurialCabinet(wlrepo.Cabinet): - def __init__(self, library, branchname, doc=None, user=None): + def __init__(self, library, branchname=None, doc=None, user=None): if doc and user: super(MercurialCabinet, self).__init__(library, doc=doc, user=user) - else: + self._branchname = library._bname(user=user, docid=doc) + elif branchname: super(MercurialCabinet, self).__init__(library, name=branchname) - - self._branchname = branchname + self._branchname = branchname + else: + raise ValueError("Provide either doc/user or branchname") def shelf(self, selector=None): - if selector is not None: - raise NotImplementedException() - - return MercurialShelf(self, self._library._changectx(self._branchname)) + return self._library.shelf(self._branchname) def documents(self): return self._execute_in_branch(action=lambda l, c: (e[1] for e in l._filelist())) @@ -250,10 +247,17 @@ class MercurialCabinet(wlrepo.Cabinet): def retrieve(self, part=None, shelf=None): name, fileid = self._filename(part) + print "Retrieving document %s from cab %s" % (name, self._name) + if fileid is None: raise wlrepo.LibraryException("Can't retrieve main document from main cabinet.") + + def retrieve_action(l,c): + if l._fileexists(fileid): + return MercurialDocument(c, name=name, fileid=fileid) + return None - return self._execute_in_branch(lambda l, c: MercurialDocument(c, name, fileid)) + return self._execute_in_branch(retrieve_action) def create(self, name, initial_data): name, fileid = self._filename(name) @@ -292,15 +296,15 @@ class MercurialCabinet(wlrepo.Cabinet): def _filename(self, part): part = self._library._sanitize_string(part) - fileid = None + docid = None if self._maindoc == '': - if part is None: return [None, None] - fileid = part + if part is None: rreeturn [None, None] + docid = part else: - fileid = self._maindoc + (('$' + part) if part else '') + docid = self._maindoc + (('$' + part) if part else '') - return fileid, 'pub_' + fileid + '.xml' + return docid, 'pub_' + docid + '.xml' def _fileopener(self): return self._library._fileopener() @@ -311,17 +315,19 @@ class MercurialCabinet(wlrepo.Cabinet): def _filectx(self, fileid): return self._library._filectx(fileid, self._branchname) + def ismain(self): + return (self._library.main_cabinet == self) + class MercurialDocument(wlrepo.Document): def __init__(self, cabinet, name, fileid): super(MercurialDocument, self).__init__(cabinet, name=name) self._opener = self._cabinet._fileopener() self._fileid = fileid - self._refresh() + self.refresh() - def _refresh(self): - self._filectx = self._cabinet._filectx(self._fileid) - self._shelf = MercurialShelf(self, self._filectx.node()) + def refresh(self): + self._filectx = self._cabinet._filectx(self._fileid) def read(self): return self._opener(self._filectx.path(), "r").read() @@ -334,96 +340,114 @@ class MercurialDocument(wlrepo.Document): self.library._commit(self._fileid, message, user) def update(self): - if self._cabinet.is_main(): - return True # always up-to-date - - mdoc = self.library.document(self._fileid) + lock = self.library._lock() + try: + if self._cabinet.ismain(): + return True # always up-to-date - mshelf = mdoc.shelf() - shelf = self.shelf() + user = self._cabinet.username or 'library' + mdoc = self.library.document(self._fileid) - if not mshelf.ancestorof(shelf) and not shelf.parentof(mshelf): - shelf.merge_with(mshelf) + mshelf = mdoc.shelf() + shelf = self.shelf() - return rc.ALL_OK + if not mshelf.ancestorof(shelf) and not shelf.parentof(mshelf): + shelf.merge_with(mshelf, user=user) - def share(self, message, user): - if self._cabinet.is_main(): - return True # always shared + return True + finally: + lock.release() - main = self.shared_version() - local = self.shelf() + def share(self, message): + lock = self.library._lock() + try: + print "sharing from", self._cabinet, self._cabinet.username + + if self._cabinet.ismain(): + return True # always shared - no_changes = True + if self._cabinet.username is None: + raise ValueError("Can only share documents from personal cabinets.") + + user = self._cabinet.username - # Case 1: - # * local - # | - # * <- can also be here! - # /| - # / | - # main * * - # | | - # The local branch has been recently updated, - # so we don't need to update yet again, but we need to - # merge down to default branch, even if there was - # no commit's since last update + main = self.shared().shelf() + local = self.shelf() - if main.ancestorof(local): - main.merge_with(local, user=user, message=message) - no_changes = False - - # Case 2: - # - # main * * local - # |\ | - # | \| - # | * - # | | - # - # Default has no changes, to update from this branch - # since the last merge of local to default. - elif main.has_common_ancestor(local): - if not local.parentof(main): + no_changes = True + + # Case 1: + # * local + # | + # * <- can also be here! + # /| + # / | + # main * * + # | | + # The local branch has been recently updated, + # so we don't need to update yet again, but we need to + # merge down to default branch, even if there was + # no commit's since last update + + if main.ancestorof(local): main.merge_with(local, user=user, message=message) no_changes = False - # Case 3: - # main * - # | - # * <- this case overlaps with previos one - # |\ - # | \ - # | * local - # | | - # - # There was a recent merge to the defaul branch and - # no changes to local branch recently. - # - # Use the fact, that user is prepared to see changes, to - # update his branch if there are any - elif local.ancestorof(main): - if not local.parentof(main): + # Case 2: + # + # main * * local + # |\ | + # | \| + # | * + # | | + # + # Default has no changes, to update from this branch + # since the last merge of local to default. + elif local.has_common_ancestor(main): + if not local.parentof(main): + main.merge_with(local, user=user, message=message) + no_changes = False + + # Case 3: + # main * + # | + # * <- this case overlaps with previos one + # |\ + # | \ + # | * local + # | | + # + # There was a recent merge to the defaul branch and + # no changes to local branch recently. + # + # Use the fact, that user is prepared to see changes, to + # update his branch if there are any + elif local.ancestorof(main): + if not local.parentof(main): + local.merge_with(main, user=user, message='Local branch update.') + no_changes = False + else: local.merge_with(main, user=user, message='Local branch update.') - no_changes = False - else: - local.merge_with(main, user=user, message='Local branch update.') - self._refresh() - local = self.shelf() + self._refresh() + local = self.shelf() - main.merge_with(local, user=user, message=message) + main.merge_with(local, user=user, message=message) + finally: + lock.release() def shared(self): - return self.library.document(self._fileid) + return self.library.main_cabinet.retrieve(self._name) + + def exists(self): + return self._cabinet.exists(self._fileid) @property def size(self): return self._filectx.size() - def shelf(self): - return self._shelf + return MercurialShelf(self.library, self._filectx.node()) @property def last_modified(self): @@ -432,12 +456,20 @@ class MercurialDocument(wlrepo.Document): def __str__(self): return u"Document(%s->%s)" % (self._cabinet.name, self._name) + def __eq__(self, other): + return self._filectx == other._filectx + + class MercurialShelf(wlrepo.Shelf): - def __init__(self, cabinet, changectx): - super(MercurialShelf, self).__init__(cabinet) - self._changectx = changectx + def __init__(self, lib, changectx): + super(MercurialShelf, self).__init__(lib) + + if isinstance(changectx, str): + self._changectx = lib._changectx(changectx) + else: + self._changectx = changectx @property def _rev(self): @@ -446,9 +478,38 @@ class MercurialShelf(wlrepo.Shelf): def __str__(self): return self._changectx.hex() + def __repr__(self): + return "MercurialShelf(%s)" % self._changectx.hex() def ancestorof(self, other): - pass + nodes = list(other._changectx._parents) + while nodes[0].node() != nullid: + v = nodes.pop(0) + if v == self._changectx: + return True + nodes.extend( v._parents ) + return False + + def parentof(self, other): + return self._changectx in other._changectx._parents + + def has_common_ancestor(self, other): + a = self._changectx.ancestor(other._changectx) + print a, self._changectx.branch(), a.branch() + + return (a.branch() == self._changectx.branch()) + + def merge_with(self, other, user, message): + lock = self._library._lock(True) + try: + self._library._checkout(self._changectx.node()) + self._library._merge(other._changectx.node()) + finally: + lock.release() + + def __eq__(self, other): + return self._changectx.node() == other._changectx.node() + class MergeStatus(object): def __init__(self, mstatus): diff --git a/lib/wlrepo/tests/test_mercurial.py b/lib/wlrepo/tests/test_mercurial.py index a4e1cfb4..69978260 100644 --- a/lib/wlrepo/tests/test_mercurial.py +++ b/lib/wlrepo/tests/test_mercurial.py @@ -29,33 +29,33 @@ class testBasicLibrary(object): shutil.rmtree(self.path, True) pass - def testOpening(self): + def test_opening(self): library = MercurialLibrary(self.path + '/cleanrepo') - def testMainCabinet(self): + def test_main_cabinett(self): library = MercurialLibrary(self.path + '/cleanrepo') mcab = library.main_cabinet - assert_equal(mcab.maindoc_name(), '') + assert_equal(mcab.maindoc_name, '') # @type mcab MercurialCabinet doclist = mcab.documents() assert_equal( list(doclist), ['valid_file']) - def testReadDocument(self): + def test_read_document(self): library = MercurialLibrary(self.path + '/testrepoI') doc = library.main_cabinet.retrieve('valid_file') assert_equal(doc.read().strip(), 'Ala ma kota') - def testReadUTF8Document(self): + def test_read_UTF8_document(self): library = MercurialLibrary(self.path + '/testrepoI') doc = library.main_cabinet.retrieve('polish_file') assert_equal(doc.read().strip(), u'Gąska!'.encode('utf-8')) - def testWriteDocument(self): + def test_write_document(self): library = MercurialLibrary(self.path + '/testrepoI') doc = library.main_cabinet.retrieve('valid_file') @@ -66,26 +66,164 @@ class testBasicLibrary(object): assert_equal(doc.read(), STRING) - def testCreateDocument(self): + def test_create_document(self): repopath = os.path.join(self.path, 'testrepoI') library = MercurialLibrary(repopath) - doc = library.main_cabinet.create("another_file") - doc.write("Some text") + doc = library.main_cabinet.create("another_file", "Some text") assert_equal( doc.read(), "Some text") assert_true( os.path.isfile( os.path.join(repopath, "pub_another_file.xml")) ) - def testSwitchBranch(self): + def test_switch_branch(self): library = MercurialLibrary(self.path + '/testrepoII') tester_cab = library.cabinet("valid_file", "tester", create=False) assert_equal( list(tester_cab.documents()), ['valid_file']) @raises(wlrepo.CabinetNotFound) - def testNoBranch(self): + def test_branch_not_found(self): library = MercurialLibrary(self.path + '/testrepoII') tester_cab = library.cabinet("ugh", "tester", create=False) + def test_no_branches(self): + library = MercurialLibrary(self.path + '/testrepoII') + n4 = library.shelf(4) + n3 = library.shelf(3) + n2 = library.shelf(2) + n1 = library.shelf(1) + n0 = library.shelf(0) + + assert_true( n3.parentof(n4) ) + assert_false( n4.parentof(n3) ) + assert_true( n0.parentof(n1) ) + assert_false( n1.parentof(n0) ) + assert_false( n0.parentof(n4) ) + + # def test_ancestor_of_simple(self): + assert_true( n3.ancestorof(n4) ) + assert_true( n2.ancestorof(n4) ) + assert_true( n1.ancestorof(n4) ) + assert_true( n0.ancestorof(n4) ) + + assert_true( n2.ancestorof(n3) ) + assert_true( n1.ancestorof(n3) ) + assert_true( n0.ancestorof(n3) ) + + assert_false( n4.ancestorof(n4) ) + assert_false( n4.ancestorof(n3) ) + assert_false( n3.ancestorof(n2) ) + assert_false( n3.ancestorof(n1) ) + assert_false( n3.ancestorof(n0) ) + + # def test_common_ancestor_simple(self): + assert_true( n3.has_common_ancestor(n4) ) + assert_true( n3.has_common_ancestor(n3) ) + assert_true( n3.has_common_ancestor(n3) ) + + + def test_once_branched(self): + library = MercurialLibrary(self.path + '/test3') + + n7 = library.shelf(7) + n6 = library.shelf(6) + n5 = library.shelf(5) + n4 = library.shelf(4) + n3 = library.shelf(3) + n2 = library.shelf(2) + + assert_true( n2.parentof(n3) ) + assert_false( n3.parentof(n2) ) + + assert_true( n2.parentof(n5) ) + assert_false( n5.parentof(n2) ) + + assert_false( n2.parentof(n4) ) + assert_false( n2.parentof(n6) ) + assert_false( n3.parentof(n5) ) + assert_false( n5.parentof(n3) ) + + # def test_ancestorof_branched(self): + assert_true( n2.ancestorof(n7) ) + assert_false( n7.ancestorof(n2) ) + assert_true( n2.ancestorof(n6) ) + assert_false( n6.ancestorof(n2) ) + assert_true( n2.ancestorof(n5) ) + assert_false( n5.ancestorof(n2) ) + + assert_false( n3.ancestorof(n5) ) + assert_false( n5.ancestorof(n3) ) + assert_false( n4.ancestorof(n5) ) + assert_false( n5.ancestorof(n4) ) + assert_false( n3.ancestorof(n7) ) + assert_false( n7.ancestorof(n3) ) + assert_false( n4.ancestorof(n6) ) + assert_false( n6.ancestorof(n4) ) + + # def test_common_ancestor_branched(self): + assert_true( n2.has_common_ancestor(n4) ) + assert_true( n2.has_common_ancestor(n7) ) + assert_true( n2.has_common_ancestor(n6) ) + + # cause it's not in the right branch + assert_false( n5.has_common_ancestor(n3) ) + assert_false( n7.has_common_ancestor(n4) ) + + def test_after_merge(self): + library = MercurialLibrary(self.path + '/test4') + n8 = library.shelf(8) + n7 = library.shelf(7) + n6 = library.shelf(6) + + assert_true( n7.parentof(n8) ) + assert_false( n8.parentof(n7) ) + + assert_true( n7.ancestorof(n8) ) + assert_true( n6.ancestorof(n8) ) + + + assert_true( n7.has_common_ancestor(n8) ) + # cause it's not in the right branch + assert_false( n8.has_common_ancestor(n7) ) + + + def test_after_merge_and_local_commit(self): + library = MercurialLibrary(self.path + '/test5b') + n9 = library.shelf(9) + n8 = library.shelf(8) + n7 = library.shelf(7) + n6 = library.shelf(6) + + assert_true( n7.parentof(n8) ) + assert_false( n8.parentof(n7) ) + + assert_true( n9.has_common_ancestor(n8) ) + # cause it's not in the right branch + assert_false( n8.has_common_ancestor(n9) ) + + + def test_merge_personal_to_default(self): + library = MercurialLibrary(self.path + '/test3') + + main = library.shelf(2) + local = library.shelf(7) + + document = library.document("ala", "admin") + shared = document.shared() + print document, shared + + document.share("Here is my copy!") + + assert_equal( document.shelf(), local) # local didn't change + + + new_main = shared.shelf() + assert_not_equal( new_main, main) # main has new revision + + # check for parents + assert_true( main.parentof(new_main) ) + assert_true( local.parentof(new_main) ) + + def testCreateBranch(self): repopath = os.path.join(self.path, 'testrepoII') @@ -96,4 +234,4 @@ class testBasicLibrary(object): - \ No newline at end of file + diff --git a/project/static/js/editor.ui.js b/project/static/js/editor.ui.js index 8932b4e6..f66d977b 100755 --- a/project/static/js/editor.ui.js +++ b/project/static/js/editor.ui.js @@ -277,7 +277,7 @@ Editor.prototype.loadSplitDialog = function(hash) hash.t.success(); }); - if($('#id_splitform-autoxml').is(':checked')) + /* if($('#id_splitform-autoxml').is(':checked')) $('#split-form-dc-subform').show(); else $('#split-form-dc-subform').hide(); @@ -287,7 +287,7 @@ Editor.prototype.loadSplitDialog = function(hash) $('#split-form-dc-subform').show(); else $('#split-form-dc-subform').hide(); - }); + }); */ }; $.ajax({ diff --git a/project/templates/explorer/split.html b/project/templates/explorer/split.html index b39c58db..8d3b67bb 100755 --- a/project/templates/explorer/split.html +++ b/project/templates/explorer/split.html @@ -18,7 +18,7 @@ +

-- 2.20.1