+ return parent_ca
+
+ if parent_ca is None or parent_ca.created_at < merge_parent_ca.created_at:
+ return merge_parent_ca
+
+ return parent_ca
+
+ def get_ancestors(self):
+ revs = set()
+ if self.parent is not None:
+ revs.add(self.parent)
+ revs.update(self.parent.get_ancestors())
+ if self.merge_parent is not None:
+ revs.add(self.merge_parent)
+ revs.update(self.merge_parent.get_ancestors())
+ return revs
+
+
+@python_2_unicode_compatible
+class Ref(models.Model):
+ """A reference pointing to a specific revision."""
+
+ revision = models.ForeignKey(
+ Revision, null=True, blank=True, default=None, verbose_name=_('revision'),
+ help_text=_("The document's revision."), editable=False)
+
+ def __str__(self):
+ return "ref:{0}->rev:{1}".format(self.id, self.revision_id)
+
+ def merge_text(self, base, local, remote):
+ """Override in subclass to have different kinds of merges."""
+ files = []
+ for f in local, base, remote:
+ temp = NamedTemporaryFile(delete=False)
+ temp.write(f)
+ temp.close()
+ files.append(temp.name)
+ p = Popen(['/usr/bin/diff3', '-mE', '-L', 'old', '-L', '', '-L', 'new'] + files, stdout=PIPE)
+ result, errs = p.communicate()
+
+ for f in files:
+ os.unlink(f)
+ return result.decode('utf-8')
+
+ def merge_with(self, revision, author=None, author_name=None, author_email=None, description="Automatic merge."):
+ """Merges a given revision into the ref."""
+ if self.revision is None:
+ fast_forward = True
+ self.revision = revision
+ elif self.revision.pk == revision.pk or self.revision.is_descendant_of(revision):
+ # Already merged.
+ return
+ elif revision.is_descendant_of(self.revision):
+ # Fast forward.
+ fast_forward = True
+ self.revision = revision
+ else:
+ # Need to create a merge revision.
+ fast_forward = False
+ base = self.revision.get_common_ancestor_with(revision)
+
+ local_text = self.materialize().encode('utf-8')
+ base_text = base.materialize().encode('utf-8')
+ other_text = revision.materialize().encode('utf-8')
+
+ merge_text = self.merge_text(base_text, local_text, other_text)
+
+ merge_revision = Revision.create(
+ text=merge_text,
+ parent=self.revision,
+ merge_parent=revision,
+ author=author,
+ author_name=author_name,
+ author_email=author_email,
+ description=description
+ )
+ self.revision = merge_revision