Fix bug #6058: Change tracking not usable in version control.

This patch makes sure that there are minimal changes when loading and saving a file with change tracking.

- the authors are assigned a buffer_id, such that when the file is saved, they get the same id,
- the authorlist is sorted according to the buffer_id,
- the buffer_id is written to the file in the author list (file format change)
- the ids start with 1, because 0 is internally reserved for the current Author,
- when writing the file, the current author is assigned an id if he didn't already have it.

git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@30756 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
Vincent van Ravesteijn 2009-07-23 20:08:05 +00:00
parent e5dfb57a9f
commit 966687d98b
11 changed files with 170 additions and 37 deletions

View File

@ -1,6 +1,10 @@
LyX file-format changes
-----------------------
2009-07-22 Vincent van Ravesteijn <vfr@lyx.org>
* Format incremented to 369: add the author ids to the list of
authors and let the numbering start with 1 in stead of 0.
2009-07-21 Jürgen Spitzmüller <spitz@lyx.org>, Uwe Stöhr <uwestoehr@web.de>
* Format incremented to 368: allow to use glue lengths for
horizontal spaces.

View File

@ -947,6 +947,76 @@ def revert_hspace_glue_lengths(document):
document.body[i:i+3] = subst
i = i + 2
def convert_author_id(document):
" Add the author_id to the \\author definition and make sure 0 is not used"
i = 0
j = 1
while True:
i = find_token(document.header, "\\author", i)
if i == -1:
break
author = document.header[i].split(' ')
name = '\"\"'
if len(author) >= 2:
name = author[1]
email = ''
if len(author) == 3:
email = author[2]
document.header[i] = "\\author %i %s %s" % (j, name, email)
j = j + 1
i = i + 1
k = 0
while True:
k = find_token(document.body, "\\change_", k)
if k == -1:
break
change = document.body[k].split(' ');
if len(change) == 3:
type = change[0]
author_id = int(change[1])
time = change[2]
document.body[k] = "%s %i %s" % (type, author_id + 1, time)
k = k + 1
def revert_author_id(document):
" Remove the author_id from the \\author definition "
i = 0
j = 0
idmap = dict()
while True:
i = find_token(document.header, "\\author", i)
if i == -1:
break
author = document.header[i].split(' ')
name = '\"\"'
if len(author) >= 3:
author_id = int(author[1])
idmap[author_id] = j
name = author[2]
email = ''
if len(author) == 4:
email = author[3]
document.header[i] = "\\author %s %s" % (name, email)
i = i + 1
j = j + 1
k = 0
while True:
k = find_token(document.body, "\\change_", k)
if k == -1:
break
change = document.body[k].split(' ');
if len(change) == 3:
type = change[0]
author_id = int(change[1])
time = change[2]
document.body[k] = "%s %i %s" % (type, idmap[author_id], time)
k = k + 1
##
# Conversion hub
@ -975,10 +1045,12 @@ convert = [[346, []],
[365, []],
[366, []],
[367, []],
[368, []]
[368, []],
[369, [convert_author_id]]
]
revert = [[367, [revert_hspace_glue_lengths]],
revert = [[368, [revert_author_id]],
[367, [revert_hspace_glue_lengths]],
[366, [revert_percent_vspace_lengths, revert_percent_hspace_lengths]],
[365, [revert_percent_skip_lengths]],
[364, [revert_paragraph_indentation]],

View File

@ -16,6 +16,7 @@
#include "support/lassert.h"
#include <algorithm>
#include <istream>
using namespace std;
@ -33,13 +34,16 @@ bool operator==(Author const & l, Author const & r)
ostream & operator<<(ostream & os, Author const & a)
{
// FIXME UNICODE
os << "\"" << to_utf8(a.name()) << "\" " << to_utf8(a.email());
os << a.buffer_id() << " \"" << to_utf8(a.name())
<< "\" " << to_utf8(a.email());
return os;
}
istream & operator>>(istream & is, Author & a)
{
string s;
is >> a.buffer_id_;
getline(is, s);
// FIXME UNICODE
a.name_ = from_utf8(trim(token(s, '\"', 1)));
@ -48,6 +52,11 @@ istream & operator>>(istream & is, Author & a)
}
bool author_smaller(Author const & lhs, Author const & rhs) {
return lhs.buffer_id() < rhs.buffer_id();
}
AuthorList::AuthorList()
: last_id_(0)
{
@ -59,13 +68,17 @@ int AuthorList::record(Author const & a)
Authors::const_iterator it(authors_.begin());
Authors::const_iterator itend(authors_.end());
for (; it != itend; ++it) {
if (it->second == a)
return it->first;
for (int i = 0; it != itend; ++it, ++i) {
if (*it == a) {
if (it->buffer_id() == 0)
// The current author is internally represented as
// author 0, but it appears he has already an id.
it->setBufferId(a.buffer_id());
return i;
}
authors_[last_id_++] = a;
return last_id_ - 1;
}
authors_.push_back(a);
return last_id_++;
}
@ -79,9 +92,8 @@ void AuthorList::record(int id, Author const & a)
Author const & AuthorList::get(int id) const
{
Authors::const_iterator it(authors_.find(id));
LASSERT(it != authors_.end(), /**/);
return it->second;
LASSERT(id < (int)authors_.size() , /**/);
return authors_[id];
}
@ -97,4 +109,43 @@ AuthorList::Authors::const_iterator AuthorList::end() const
}
void AuthorList::sort() {
std::sort(authors_.begin(), authors_.end(), author_smaller);
}
ostream & operator<<(ostream & os, AuthorList const & a) {
// Copy the authorlist, because we don't want to sort the original
AuthorList sorted = a;
sorted.sort();
AuthorList::Authors::const_iterator a_it = sorted.begin();
AuthorList::Authors::const_iterator a_end = sorted.end();
// Find the buffer id for the current author (internal id 0),
// if he doesn't have a buffer_id yet.
if (sorted.get(0).buffer_id() == 0) {
unsigned int cur_id = 1;
for (; a_it != a_end; ++a_it) {
if (a_it->buffer_id() == cur_id)
++cur_id;
else if (a_it->buffer_id() > cur_id) {
break;
}
}
// Set the id in both the original authorlist,
// as in the copy.
a.get(0).setBufferId(cur_id);
sorted.get(0).setBufferId(cur_id);
sorted.sort();
}
for (a_it = sorted.begin(); a_it != a_end; ++a_it) {
if (a_it->used())
os << "\\author " << *a_it << "\n";
}
return os;
}
} // namespace lyx

View File

@ -25,12 +25,16 @@ public:
Author() {}
///
Author(docstring const & name, docstring const & email)
: name_(name), email_(email), used_(true) {}
: name_(name), email_(email), used_(true), buffer_id_(0) {}
///
docstring name() const { return name_; }
///
docstring email() const { return email_; }
///
unsigned int buffer_id() const { return buffer_id_; }
///
void setBufferId(unsigned int buffer_id) const { buffer_id_ = buffer_id; }
///
void setUsed(bool u) const { used_ = u; }
///
bool used() const { return used_; }
@ -42,6 +46,8 @@ private:
docstring name_;
/// The author's email address
docstring email_;
/// The id of the author in the lyx-file
mutable unsigned int buffer_id_;
///
mutable bool used_;
};
@ -58,12 +64,16 @@ public:
///
Author const & get(int id) const;
///
typedef std::map<int, Author> Authors;
typedef std::vector<Author> Authors;
///
void sort();
///
Authors::const_iterator begin() const;
///
Authors::const_iterator end() const;
///
friend
std::ostream & operator<<(std::ostream & os, AuthorList const & a);
private:
///
int last_id_;

View File

@ -127,7 +127,7 @@ namespace {
// Do not remove the comment below, so we get merge conflict in
// independent branches. Instead add your own.
int const LYX_FORMAT = 368; // spitz, uwestoehr: glue lengths for the HSpace dialog
int const LYX_FORMAT = 369; // vfr: add author ids to list of authors
typedef map<string, bool> DepClean;
typedef map<docstring, pair<InsetLabel const *, Buffer::References> > RefCache;
@ -1014,7 +1014,7 @@ bool Buffer::write(ostream & ofs) const
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)
a_it->second.setUsed(false);
a_it->setUsed(false);
ParIterator const end = const_cast<Buffer *>(this)->par_iterator_end();
ParIterator it = const_cast<Buffer *>(this)->par_iterator_begin();

View File

@ -687,7 +687,7 @@ string BufferParams::readToken(Lexer & lex, string const & token,
istringstream ss(lex.getString());
Author a;
ss >> a;
author_map.push_back(pimpl_->authorlist.record(a));
author_map[a.buffer_id()] = pimpl_->authorlist.record(a);
} else if (token == "\\paperorientation") {
string orient;
lex >> orient;
@ -954,14 +954,7 @@ void BufferParams::writeFile(ostream & os) const
os << "\\tracking_changes " << convert<string>(trackChanges) << "\n";
os << "\\output_changes " << convert<string>(outputChanges) << "\n";
AuthorList::Authors::const_iterator a_it = pimpl_->authorlist.begin();
AuthorList::Authors::const_iterator a_end = pimpl_->authorlist.end();
for (; a_it != a_end; ++a_it) {
if (a_it->second.used())
os << "\\author " << a_it->second << "\n";
else
os << "\\author " << Author() << "\n";
}
os << pimpl_->authorlist;
}

View File

@ -309,8 +309,8 @@ public:
AuthorList & authors();
AuthorList const & authors() const;
/// map of the file's author IDs to buffer author IDs
std::vector<unsigned int> author_map;
/// map of the file's author IDs to AuthorList indexes
std::map<unsigned int, int> author_map;
///
std::string const dvips_options() const;
/** The return value of paperSizeName() depends on the

View File

@ -369,7 +369,7 @@ int Changes::latexMarkChange(odocstream & os, BufferParams const & bparams,
}
void Changes::lyxMarkChange(ostream & os, int & column,
void Changes::lyxMarkChange(ostream & os, BufferParams const & bparams, int & column,
Change const & old, Change const & change)
{
if (old == change)
@ -377,19 +377,21 @@ void Changes::lyxMarkChange(ostream & os, int & column,
column = 0;
int const buffer_id = bparams.authors().get(change.author).buffer_id();
switch (change.type) {
case Change::UNCHANGED:
os << "\n\\change_unchanged\n";
break;
case Change::DELETED: {
os << "\n\\change_deleted " << change.author
os << "\n\\change_deleted " << buffer_id
<< " " << change.changetime << "\n";
break;
}
case Change::INSERTED: {
os << "\n\\change_inserted " << change.author
os << "\n\\change_inserted " << buffer_id
<< " " << change.changetime << "\n";
break;
}

View File

@ -103,8 +103,8 @@ public:
Change const & oldChange, Change const & change);
/// output .lyx file format for transitions between changes
static void lyxMarkChange(std::ostream & os, int & column,
Change const & old, Change const & change);
static void lyxMarkChange(std::ostream & os, BufferParams const & bparams,
int & column, Change const & old, Change const & change);
///
void checkAuthors(AuthorList const & authorList);

View File

@ -1194,7 +1194,7 @@ void Paragraph::write(ostream & os, BufferParams const & bparams,
for (pos_type i = 0; i <= size(); ++i) {
Change const change = lookupChange(i);
Changes::lyxMarkChange(os, column, running_change, change);
Changes::lyxMarkChange(os, bparams, column, running_change, change);
running_change = change;
if (i == size())

View File

@ -224,16 +224,17 @@ void readParToken(Buffer const & buf, Paragraph & par, Lexer & lex,
unsigned int aid;
time_t ct;
is >> aid >> ct;
if (aid >= bp.author_map.size()) {
map<unsigned int, int> 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, bp.author_map[aid], ct);
change = Change(Change::INSERTED, am.find(aid)->second, ct);
else
change = Change(Change::DELETED, bp.author_map[aid], ct);
change = Change(Change::DELETED, am.find(aid)->second, ct);
}
} else {
lex.eatLine();