Dear list,

The patch below fixes bug #9854. (<http://www.lyx.org/trac/ticket/9854>)
Find more details in the commit log.

I have two questions:

* I had to remove a const qualifier in Text::readParToken (see patch
below). Do I need to put it back? If so, what's the proper way of
adding the dummy author?

* Georg, you told us an old story of "FIXME: UNICODE"s being placed in
the source when the developers decided that docstring would be the only
place where we see Unicode. I am happy to fix these as I go through the
code, but I am not sure that I understood what's the problem typically.
Here have a look at operator>> in Author.cpp below. What's the problem
and what should typically be done there?

Scott, this can wait until after alpha, as you want.


Sincerely,
Guillaume
>From d6a7ae1ab965f9cde4b0dd5df1716f50aafbe010 Mon Sep 17 00:00:00 2001
From: Guillaume Munch <g...@lyx.org>
Date: Wed, 11 Nov 2015 21:31:05 +0000
Subject: [PATCH] Fix bug #9854 "Dataloss after git merge with change tracking"

A plausible scenario is that change tracking is used together with a versioning
system. In this case, parallel modifications might remove an \author line on one
side, and add another change of this author on the other side. This scenario
causes a bad merge after which the added change has no associated author. In
this case, LyX used to display a list of errors on opening and deliberately
removed the corresponding change tracking information.

* If ever a tracked change refers to an author that does not exist, then add a
  dummy author. This dummy author is replaced by the real author at the next
  occasion. (Fixes the main cause of data loss.)

* Keep all \author lines that do not correspond to the current author. (Play
  nice with git.)
---
 src/Author.cpp       | 42 +++++++++++++++++++++++++++++-------------
 src/Author.h         |  4 ++--
 src/Buffer.cpp       | 14 +++++++++++---
 src/BufferParams.cpp | 11 ++++++++++-
 src/BufferParams.h   |  6 ++++--
 src/Text.cpp         | 17 +++++++++--------
 6 files changed, 65 insertions(+), 29 deletions(-)

diff --git a/src/Author.cpp b/src/Author.cpp
index 79e7de0..24ab0c6 100644
--- a/src/Author.cpp
+++ b/src/Author.cpp
@@ -12,6 +12,7 @@
 
 #include "Author.h"
 
+#include "support/convert.h"
 #include "support/lassert.h"
 #include "support/lstrings.h"
 
@@ -36,10 +37,15 @@ static int computeHash(docstring const & name,
 
 
 Author::Author(docstring const & name, docstring const & email)
-	: name_(name), email_(email), used_(true)
-{
-	buffer_id_ = computeHash(name_, email_);
-}
+	: name_(name), email_(email), used_(true),
+	  buffer_id_(computeHash(name, email))
+{}
+
+
+Author::Author(int buffer_id)
+	: name_(convert<docstring>(buffer_id)), email_(docstring()), used_(true),
+	  buffer_id_(buffer_id)
+{}
 
 
 bool operator==(Author const & l, Author const & r)
@@ -66,6 +72,7 @@ istream & operator>>(istream & is, Author & a)
 	// FIXME UNICODE
 	a.name_ = from_utf8(trim(token(s, '\"', 1)));
 	a.email_ = from_utf8(trim(token(s, '\"', 2)));
+	a.used_ = true;
 	return is;
 }
 
@@ -77,7 +84,6 @@ bool author_smaller(Author const & lhs, Author const & rhs)
 
 
 AuthorList::AuthorList()
-	: last_id_(0)
 {}
 
 
@@ -89,14 +95,24 @@ int AuthorList::record(Author const & a)
 	if (!authors_.empty() && a == authors_[0])
 		authors_[0].setBufferId(a.bufferId());
 
-	Authors::const_iterator it(authors_.begin());
-	Authors::const_iterator itend(authors_.end());
-	for (int i = 0;  it != itend; ++it, ++i) {
+	Authors::const_iterator it = authors_.begin();
+	Authors::const_iterator const beg = it;
+	Authors::const_iterator const end = authors_.end();
+	for (;  it != end; ++it) {
 		if (*it == a)
-			return i;
+			return it - beg;
+		if (it->bufferId() == a.bufferId()) {
+			int id = it - beg;
+			// bug #9854
+			// If the name is equal to the buffer_id then it means that the
+			// \author line was missing. We restore this information.
+			if (it->name() == convert<docstring>(it->bufferId()))
+				record(id, a);
+			return id;
+		}
 	}
 	authors_.push_back(a);
-	return last_id_++;
+	return authors_.size() - 1;
 }
 
 
@@ -146,9 +162,9 @@ ostream & operator<<(ostream & os, AuthorList const & a)
 	sorted.sort();
 
 	AuthorList::Authors::const_iterator a_it = sorted.begin();
-	AuthorList::Authors::const_iterator a_end = sorted.end();
-	
-	for (a_it = sorted.begin(); a_it != a_end; ++a_it) {
+	AuthorList::Authors::const_iterator const a_end = sorted.end();
+
+	for (; a_it != a_end; ++a_it) {
 		if (a_it->used())
 			os << "\\author " << *a_it << "\n";	
 	}
diff --git a/src/Author.h b/src/Author.h
index f798b04..9805218 100644
--- a/src/Author.h
+++ b/src/Author.h
@@ -25,6 +25,8 @@ public:
 	Author() : used_(false), buffer_id_(0) {};
 	///
 	Author(docstring const & name, docstring const & email);
+	/// For when the \author line is missing
+	Author(int buffer_id);
 	///
 	docstring name() const { return name_; }
 	///
@@ -79,8 +81,6 @@ public:
 	std::ostream & operator<<(std::ostream & os, AuthorList const & a);
 private:
 	///
-	int last_id_;
-	///
 	Authors authors_;
 };
 
diff --git a/src/Buffer.cpp b/src/Buffer.cpp
index f2eae77..edf8466 100644
--- a/src/Buffer.cpp
+++ b/src/Buffer.cpp
@@ -1597,11 +1597,19 @@ bool Buffer::write(ostream & ofs) const
 	    << "\\lyxformat " << LYX_FORMAT << "\n"
 	    << "\\begin_document\n";
 
-	/// For each author, set 'used' to true if there is a change
-	/// by this author in the document; otherwise set it to 'false'.
+	/// For each author, we used to set 'used' to false if there is no change by
+	/// this author in the document. But a plausible scenario is that change
+	/// tracking is used together with a versioning system. In this case,
+	/// parallel modifications might remove an \author line on one side, and add
+	/// another change of this author on the other side. This scenario causes a
+	/// bad merge after which the added change has no associated author. The new
+	/// behaviour is that only the \author line corresponding to the current
+	/// author is possibly removed. (Bug #9854 "Dataloss after git merge with
+	/// change tracking")
 	AuthorList::Authors::const_iterator a_it = params().authors().begin();
 	AuthorList::Authors::const_iterator a_end = params().authors().end();
-	for (; a_it != a_end; ++a_it)
+	//for (; a_it != a_end; ++a_it)
+	if (a_it != a_end)//#9854
 		a_it->setUsed(false);
 
 	ParIterator const end = const_cast<Buffer *>(this)->par_iterator_end();
diff --git a/src/BufferParams.cpp b/src/BufferParams.cpp
index c7ec167..50103aa 100644
--- a/src/BufferParams.cpp
+++ b/src/BufferParams.cpp
@@ -429,6 +429,9 @@ BufferParams::BufferParams()
 
 	output_sync = false;
 	use_refstyle = true;
+
+	// map current author
+	author_map_[pimpl_->authorlist.get(0).bufferId()] = 0;
 }
 
 
@@ -504,6 +507,12 @@ AuthorList const & BufferParams::authors() const
 }
 
 
+void BufferParams::addAuthor(Author a)
+{
+	author_map_[a.bufferId()] = pimpl_->authorlist.record(a);
+}
+
+
 BranchList & BufferParams::branchlist()
 {
 	return pimpl_->branchlist;
@@ -876,7 +885,7 @@ string BufferParams::readToken(Lexer & lex, string const & token,
 		istringstream ss(lex.getString());
 		Author a;
 		ss >> a;
-		author_map[a.bufferId()] = pimpl_->authorlist.record(a);
+		addAuthor(a);
 	} else if (token == "\\paperorientation") {
 		string orient;
 		lex >> orient;
diff --git a/src/BufferParams.h b/src/BufferParams.h
index 6b2bb47..946ae90 100644
--- a/src/BufferParams.h
+++ b/src/BufferParams.h
@@ -15,6 +15,7 @@
 #ifndef BUFFERPARAMS_H
 #define BUFFERPARAMS_H
 
+#include "Author.h"
 #include "Citation.h"
 #include "DocumentClassPtr.h"
 #include "Format.h"
@@ -33,7 +34,6 @@ namespace lyx {
 
 namespace support { class FileName; }
 
-class AuthorList;
 class BranchList;
 class Bullet;
 class DocumentClass;
@@ -392,10 +392,12 @@ public:
 	/// the author list for the document
 	AuthorList & authors();
 	AuthorList const & authors() const;
+	void addAuthor(Author a);
 
 	/// map of the file's author IDs to AuthorList indexes
 	typedef std::map<int, int> AuthorMap;
-	AuthorMap author_map;
+	AuthorMap author_map_;
+
 	/// the buffer's active font encoding
 	std::string const font_encoding() const;
 	/// all font encodings requested by the prefs/document/main language.
diff --git a/src/Text.cpp b/src/Text.cpp
index 67776a0..497dd03 100644
--- a/src/Text.cpp
+++ b/src/Text.cpp
@@ -360,7 +360,9 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
 	string const & token, Font & font, Change & change, ErrorList & errorList)
 {
 	Buffer * buf = const_cast<Buffer *>(&owner_->buffer());
-	BufferParams const & bp = buf->params();
+	//FIXME: put const back
+	BufferParams & bp = buf->params();
+	//BufferParams const & bp = buf->params();
 
 	if (token[0] != '\\') {
 		docstring dstr = lex.getDocString();
@@ -534,18 +536,17 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
 		int aid;
 		time_t ct;
 		is >> aid >> ct;
-		BufferParams::AuthorMap const & am = bp.author_map;
+		BufferParams::AuthorMap const & am = bp.author_map_;
 		if (am.find(aid) == am.end()) {
 			errorList.push_back(ErrorItem(_("Change tracking error"),
 					    bformat(_("Unknown author index for change: %1$d\n"), aid),
 					    par.id(), 0, par.size()));
-			change = Change(Change::UNCHANGED);
-		} else {
-			if (token == "\\change_inserted")
-				change = Change(Change::INSERTED, am.find(aid)->second, ct);
-			else
-				change = Change(Change::DELETED, am.find(aid)->second, ct);
+			bp.addAuthor(Author(aid));
 		}
+		if (token == "\\change_inserted")
+			change = Change(Change::INSERTED, am.find(aid)->second, ct);
+		else
+			change = Change(Change::DELETED, am.find(aid)->second, ct);
 	} else {
 		lex.eatLine();
 		errorList.push_back(ErrorItem(_("Unknown token"),
-- 
2.1.4

Reply via email to