def get_merge(self): """ Return a 3-way merged docstring, or None if no merge is necessary. Updates the merge status of the docstring. Returns ------- result : {None, str} None if no merge is needed, else return the merge result. """ if self.base_doc == self.source_doc: # Nothing to merge if self.merge_status != MERGE_NONE: self.merge_status = MERGE_NONE self.save() return None if self.revisions.count() == 0: # No local edits self.merge_status = MERGE_NONE self.base_doc = self.source_doc self.save() return None if self.text == self.source_doc: # Local text agrees with VCS source, no merge needed self.merge_status = MERGE_NONE self.base_doc = self.source_doc self.save() return None result, conflicts = merge_3way( strip_spurious_whitespace(self.text) + "\n", strip_spurious_whitespace(self.base_doc) + "\n", strip_spurious_whitespace(self.source_doc) + "\n", ) result = strip_spurious_whitespace(result) if not conflicts: self.merge_status = MERGE_MERGE else: self.merge_status = MERGE_CONFLICT self.save() return result
def _import_docstring_revisions_from_xml(stream): tree = etree.parse(stream) root = tree.getroot() for el in root: if el.tag not in ('module', 'class', 'callable', 'object'): continue try: doc = Docstring.on_site.get(name=el.attrib['id']) except Docstring.DoesNotExist: print "DOES-NOT-EXIST", el.attrib['id'] continue if el.text: doc.edit(strip_spurious_whitespace(el.text.decode('string-escape')), "xml-import", comment="Imported")
def edit(self, new_text, author, comment): """Create a new revision of the page""" new_text = strip_spurious_whitespace(new_text) rev = WikiPageRevision(page=self, author=author, text=new_text, comment=comment) rev.save()
def edit(self, new_text, author, comment): """ Create a new revision of the docstring with given content. Also resolves merge conflicts. Does not create a new revision if there are no changes in the text. Raises ------ Docstring.MergeConflict If the new text still contains conflict markers. """ new_text = strip_spurious_whitespace(new_text) if self.type_code == "dir": raise RuntimeError("'dir' docstrings cannot be edited") if "<<<<<<" in new_text or ">>>>>>" in new_text: raise RuntimeError("New text still contains merge conflict markers") # assume any merge was OK self.merge_status = MERGE_NONE self.base_doc = self.source_doc self.save() # Update dirtiness self.dirty = self.source_doc != new_text # Editing 'file' docstrings can resurrect them from obsoletion, # or hide them (ie. remove their connection to their parent 'dir') if self.type_code == "file" and new_text and self.is_obsolete: # make not obsolete self.timestamp = Docstring.get_current_timestamp() self.save() self._add_to_parent() elif self.type_code == "file" and not new_text: # hide self._remove_aliases() # Add a revision (if necessary) if new_text != self.text: new_review_code = { REVIEW_NEEDS_EDITING: REVIEW_BEING_WRITTEN, REVIEW_NEEDS_WORK: REVIEW_REVISED, REVIEW_NEEDS_PROOF: REVIEW_REVISED, REVIEW_PROOFED: REVIEW_REVISED, }.get(self.review, self.review) if self.revisions.count() == 0: # Store the VCS revision the initial edit was based on, # for making statistics later on. base_rev = DocstringRevision( docstring=self, text=self.source_doc, author="Source", comment="Initial source revision", review_code=self.review, ok_to_apply=False, ) base_rev.timestamp = self.timestamp base_rev.save() rev = DocstringRevision( docstring=self, text=new_text, author=author, comment=comment, review_code=new_review_code, ok_to_apply=False, ) rev.save() # Save self.save() # Update cross-reference and toctree caches LabelCache.cache_docstring(self) ToctreeCache.cache_docstring(self) self._update_title()
def _update_docstrings_from_xml(site, stream): tree = etree.parse(stream) root = tree.getroot() timestamp = datetime.datetime.now() known_entries = {} for el in root: if el.tag not in ('module', 'class', 'callable', 'object', 'dir', 'file'): continue known_entries[el.attrib['id']] = True for el in root: if el.tag not in ('module', 'class', 'callable', 'object', 'dir', 'file'): continue bases = [] for b in el.findall('base'): bases.append(b.attrib['ref']) bases = " ".join(bases) if not bases: bases = None if el.text: docstring = strip_spurious_whitespace(el.text.decode('string-escape')) else: docstring = u"" if not isinstance(docstring, unicode): try: docstring = docstring.decode('utf-8') except UnicodeError: docstring = docstring.decode('iso-8859-1') try: line = int(el.get('line')) except (ValueError, TypeError): line = None doc, created = Docstring.on_site.get_or_create(name=el.attrib['id'], site=site) doc.type_code = el.tag doc.type_name = el.get('type') doc.argspec = el.get('argspec') doc.objclass = el.get('objclass') doc.bases = bases doc.file_name = el.get('file') doc.line_number = line doc.timestamp = timestamp if created: # New docstring doc.merge_status = MERGE_NONE doc.base_doc = doc.source_doc doc.source_doc = docstring doc.dirty = False elif docstring != doc.base_doc: # Source has changed, try to merge from base doc.source_doc = docstring doc.save() doc.get_merge() # update merge status doc.dirty = (doc.source_doc != doc.text) doc.contents.all().delete() doc.save() # -- Contents for ref in el.findall('ref'): alias = DocstringAlias() alias.target = ref.attrib['ref'] alias.parent = doc alias.alias = ref.attrib['name'] alias.save() # -- Handle obsoletion of 'file' pages missing in SVN for doc in Docstring.on_site.filter(type_code='file', timestamp__lt=timestamp).all(): doc.source_doc = "" if doc.text != doc.base_doc and doc.text != "": # Non-empty docstrings won't become obsolete, but may cause # a merge conflict doc.timestamp = timestamp doc.dirty = True doc.save() if doc.base_doc != doc.source_doc: doc.get_merge() # -- Handle obsoletion of 'dir' pages missing in SVN for doc in Docstring.on_site.filter(type_code='dir', timestamp__lt=timestamp).all(): # Only 'dir' pages with no remaining children become obsolete children = Docstring.get_non_obsolete().filter( name__startswith=doc.name + '/').all() if not children: continue else: # For others, insert deduced children doc.timestamp = timestamp for child in children: alias = DocstringAlias() alias.target = child.name alias.parent = doc alias.alias = child.name.split('/')[-1] alias.save() doc.save() # -- Update label cache LabelCache.clear(site=site) from django.db import connection, transaction cursor = connection.cursor() # -- Insert docstring names at once using raw SQL (fast!) # direct names cursor.execute(""" INSERT INTO docweb_labelcache (label, target, title, site_id) SELECT d.name, d.name, d.name, %s FROM docweb_docstring AS d WHERE d.site_id = %s AND d.timestamp = %s """, [site.id, site.id, timestamp]) # 1st dereference level (normal docstrings) cursor.execute(port_sql(""" INSERT INTO docweb_labelcache (label, target, title, site_id) SELECT d.name || '.' || a.alias, a.target, a.alias, %s FROM docweb_docstring AS d LEFT JOIN docweb_docstringalias AS a ON d.name = a.parent_id WHERE d.name || '.' || a.alias != a.target AND d.type_ != 'dir' AND d.site_id = %s AND d.timestamp = %s """), [site.id, site.id, timestamp]) # 1st dereference level (for .rst pages; they can have only 1 level) cursor.execute(port_sql(""" INSERT INTO docweb_labelcache (label, target, title, site_id) SELECT d.name || '/' || a.alias, a.target, a.alias, %s FROM docweb_docstring AS d LEFT JOIN docweb_docstringalias AS a ON d.name = a.parent_id WHERE d.name || '/' || a.alias != a.target AND d.type_ = 'dir' AND d.site_id = %s AND d.timestamp = %s """), [site.id, site.id, timestamp]) # -- Raw SQL needs a manual flush transaction.commit_unless_managed() # -- Do the part of the work that's not possible using SQL only for doc in Docstring.get_non_obsolete().filter(type_code='file').all(): LabelCache.cache_docstring_labels(doc) ToctreeCache.cache_docstring(doc) doc._update_title()