The attached patch implements two new VCS commands which I already announced 
some months ago. Only svn supports them fully, but I implemented a poor mans 
version for CVS and RCS. These commands are useful i several cases, e.g. if 
you give a lecture that is evolving each year, and want to keep your old 
files for reference, but still retain version history for the new files. The 
benefit of builtin support instead of using the VCS client of your choice is 
the correct updating of relative paths of included files (as we do already 
for save as).

OK to go in? If yes, I would test CVS/RCS more thoroughly, and add 
documentation to Additional.lyx.


Georg
diff --git a/lib/ui/stdmenus.inc b/lib/ui/stdmenus.inc
index ad8e541..f1d1af4 100644
--- a/lib/ui/stdmenus.inc
+++ b/lib/ui/stdmenus.inc
@@ -74,6 +74,8 @@ Menuset
 		OptItem "Register...|R" "vc-register"
 		OptItem "Check In Changes...|I" "vc-check-in"
 		OptItem "Check Out for Edit|O" "vc-check-out"
+		OptItem "Copy|p" "vc-copy"
+		OptItem "Rename|R" "vc-rename"
 		OptItem "Update Local Directory From Repository|d" "vc-repo-update"
 		OptItem "Revert to Repository Version|v" "vc-revert"
 		OptItem "Undo Last Check In|U" "vc-undo-last"
diff --git a/src/FuncCode.h b/src/FuncCode.h
index 8b8e874..1441f00 100644
--- a/src/FuncCode.h
+++ b/src/FuncCode.h
@@ -453,6 +453,9 @@ enum FuncCode
 	LFUN_BRANCH_MASTER_ACTIVATE,    // spitz 20120930
 	LFUN_BRANCH_MASTER_DEACTIVATE,  // spitz 20120930
 	LFUN_ENVIRONMENT_SPLIT,         // spitz 20121223
+	LFUN_VC_RENAME,                 // gb 20130122
+	LFUN_VC_COPY,                   // gb 20130122
+	// 355
 	LFUN_LASTACTION                 // end of the table
 };
 
diff --git a/src/LyXAction.cpp b/src/LyXAction.cpp
index df22d07..08a09a4 100644
--- a/src/LyXAction.cpp
+++ b/src/LyXAction.cpp
@@ -2147,6 +2147,32 @@ void LyXAction::init()
  */
 		{ LFUN_VC_REGISTER, "vc-register", ReadOnly, System },
 /*!
+ * \var lyx::FuncCode lyx::LFUN_VC_RENAME
+ * \li Action: Renames the document to another name.
+ * \li Notion: Renaming with revision history is only supported by SVN.
+               For RCS and CVS it is simulated by adding the document
+               under a new name and deleting the old one.
+ * \li Syntax: vc-rename <FILENAME> 
+ * \li Params: <FILENAME>: New name of the document.\n
+ *             A file dialog is opened if no filename is given.
+ * \li Origin: gb, 22 Jan 2013
+ * \endvar
+ */
+		{ LFUN_VC_RENAME, "vc-rename", ReadOnly, System },
+/*!
+ * \var lyx::FuncCode lyx::LFUN_VC_COPY
+ * \li Action: Copies the document to another name.
+ * \li Notion: Copying with revision history is only supported by SVN.
+               For RCS and CVS it is simulated by adding the document
+               under a new name.
+ * \li Syntax: vc-copy <FILENAME> 
+ * \li Params: <FILENAME>: New name of the document.\n
+ *             A file dialog is opened if no filename is given.
+ * \li Origin: gb, 22 Jan 2013
+ * \endvar
+ */
+		{ LFUN_VC_COPY, "vc-copy", ReadOnly, System },
+/*!
  * \var lyx::FuncCode lyx::LFUN_VC_CHECK_IN
  * \li Action: Checks-in/commits the changes of the registered file to the repository.
  * \li Notion: In RCS case this also unlocks the file.
diff --git a/src/LyXVC.cpp b/src/LyXVC.cpp
index 244dff1..174452e 100644
--- a/src/LyXVC.cpp
+++ b/src/LyXVC.cpp
@@ -18,6 +18,8 @@
 #include "LyXVC.h"
 #include "VCBackend.h"
 #include "Buffer.h"
+#include "FuncRequest.h"
+#include "LyX.h"
 
 #include "frontends/alert.h"
 
@@ -173,6 +175,44 @@ bool LyXVC::registrer()
 }
 
 
+string LyXVC::rename(FileName const & fn)
+{
+	LYXERR(Debug::LYXVC, "LyXVC: rename");
+	if (!vcs || fileInVC(fn))
+		return string();
+	docstring response;
+	bool ok = Alert::askForText(response, _("LyX VC: Log message"),
+			_("(no log message)"));
+	if (!ok) {
+		LYXERR(Debug::LYXVC, "LyXVC: user cancelled");
+		return string();
+	}
+	if (response.empty())
+		response = _("(no log message)");
+	string ret = vcs->rename(fn, to_utf8(response));
+	return ret;
+}
+
+
+string LyXVC::copy(FileName const & fn)
+{
+	LYXERR(Debug::LYXVC, "LyXVC: copy");
+	if (!vcs || fileInVC(fn))
+		return string();
+	docstring response;
+	bool ok = Alert::askForText(response, _("LyX VC: Log message"),
+			_("(no log message)"));
+	if (!ok) {
+		LYXERR(Debug::LYXVC, "LyXVC: user cancelled");
+		return string();
+	}
+	if (response.empty())
+		response = _("(no log message)");
+	string ret = vcs->copy(fn, to_utf8(response));
+	return ret;
+}
+
+
 string LyXVC::checkIn()
 {
 	LYXERR(Debug::LYXVC, "LyXVC: checkIn");
@@ -276,6 +316,7 @@ void LyXVC::toggleReadOnly()
 		checkIn();
 		break;
 	case VCS::NOLOCKING:
+	case VCS::UNVERSIONED:
 		break;
 	}
 }
@@ -284,7 +325,7 @@ void LyXVC::toggleReadOnly()
 bool LyXVC::inUse() const
 {
 	if (vcs)
-		return true;
+		return vcs->status() != VCS::UNVERSIONED;
 	return false;
 }
 
diff --git a/src/LyXVC.h b/src/LyXVC.h
index a860bda..b9564f1 100644
--- a/src/LyXVC.h
+++ b/src/LyXVC.h
@@ -77,6 +77,11 @@ public:
 	// by the next multiple messages on the top of the processed dispatch
 	// machinery.
 
+	///
+	std::string rename(support::FileName const &);
+	///
+	std::string copy(support::FileName const &);
+
 	/// Unlock and commit changes. Returns log.
 	std::string checkIn();
 	/// Does the current VC supports this operation?
@@ -138,6 +143,7 @@ public:
 	void toggleReadOnly();
 
 	/// Is the document under administration by VCS?
+	/// returns false for unregistered documents in a path managed by VCS
 	bool inUse() const;
 
 	/// Returns the RCS + version number for messages
diff --git a/src/VCBackend.cpp b/src/VCBackend.cpp
index 9841707..598b0c8 100644
--- a/src/VCBackend.cpp
+++ b/src/VCBackend.cpp
@@ -233,6 +233,35 @@ void RCS::registrer(string const & msg)
 }
 
 
+string RCS::rename(support::FileName const & newFile, string const & msg)
+{
+	// RCS has no real rename command, so we create a poor mans version
+	support::FileName const oldFile(owner_->absFileName());
+	string ret = copy(newFile, msg);
+	if (ret.empty())
+		return ret;
+	if (!oldFile.removeFile())
+		return string();
+	return ret;
+}
+
+
+string RCS::copy(support::FileName const & newFile, string const & msg)
+{
+	// RCS has no real copy command, so we create a poor mans version
+	support::FileName const oldFile(owner_->absFileName());
+	if (!oldFile.copyTo(newFile))
+		return string();
+	FileName path(oldFile.onlyPath());
+	string relFile(to_utf8(newFile.relPath(path.absFileName())));
+	string cmd = "ci -q -u -i -t-\"";
+	cmd += msg;
+	cmd += "\" ";
+	cmd += quoteName(relFile);
+	return doVCCommand(cmd, path) ? string() : "RCS: Proceeded";
+}
+
+
 string RCS::checkIn(string const & msg)
 {
 	int ret = doVCCommand("ci -q -u -m\"" + msg + "\" "
@@ -366,7 +395,7 @@ bool RCS::toggleReadOnlyEnabled()
 	// This got broken somewhere along lfuns dispatch reorganization.
 	// reloadBuffer would be needed after this, but thats problematic
 	// since we are inside Buffer::dispatch.
-	// return true;
+	// return return status() != UNVERSIONED;
 	return false;
 }
 
@@ -515,6 +544,7 @@ void CVS::scanMaster()
 	LYXERR(Debug::LYXVC, "\tlooking for `" << tmpf << '\'');
 	string line;
 	static regex const reg("/(.*)/(.*)/(.*)/(.*)/(.*)");
+	vcstatus = UNVERSIONED;
 	while (getline(ifs, line)) {
 		LYXERR(Debug::LYXVC, "\t  line: " << line);
 		if (contains(line, tmpf)) {
@@ -708,6 +738,31 @@ void CVS::registrer(string const & msg)
 }
 
 
+string CVS::rename(support::FileName const & newFile, string const & msg)
+{
+	// CVS has no real rename command, so we create a poor mans version
+	support::FileName const oldFile(owner_->absFileName());
+	string ret = copy(newFile, msg);
+	if (ret.empty())
+		return ret;
+	string cmd = "cvs -q remove -m \"" + msg + "\" " +
+		quoteName(oldFile.onlyFileName());
+	FileName path(oldFile.onlyPath());
+	return doVCCommand(cmd, path) ? string() : "CVS: Proceeded";
+}
+
+
+string CVS::copy(support::FileName const & newFile, string const & msg)
+{
+	// CVS has no real copy command, so we create a poor mans version
+	support::FileName const oldFile(owner_->absFileName());
+	FileName path(oldFile.onlyPath());
+	string relFile(to_utf8(newFile.relPath(path.absFileName())));
+	string cmd("cvs -q add -m \"" + msg + "\" " + quoteName(relFile));
+	return doVCCommand(cmd, path) ? string() : "CVS: Proceeded";
+}
+
+
 void CVS::getDiff(OperationMode opmode, FileName const & tmpf)
 {
 	doVCCommandWithOutput("cvs diff " + getTarget(opmode),
@@ -1089,14 +1144,18 @@ FileName const SVN::findFile(FileName const & file)
 
 void SVN::scanMaster()
 {
-	// vcstatus code is somewhat superflous, until we want
-	// to implement read-only toggle for svn.
-	vcstatus = NOLOCKING;
-	if (checkLockMode()) {
-		if (isLocked()) {
-			vcstatus = LOCKED;
-		} else {
-			vcstatus = UNLOCKED;
+	// vcstatus code other than UNVERSIONED is somewhat superflous,
+	// until we want to implement read-only toggle for svn.
+	FileName f = findFile(owner_->fileName());
+	if (f.empty()) {
+		vcstatus = UNVERSIONED;
+	} else {
+		vcstatus = NOLOCKING;
+		if (checkLockMode()) {
+			if (isLocked())
+				vcstatus = LOCKED;
+			else
+				vcstatus = UNLOCKED;
 		}
 	}
 }
@@ -1158,18 +1217,61 @@ void SVN::registrer(string const & /*msg*/)
 }
 
 
+string SVN::rename(support::FileName const & newFile, string const & msg)
+{
+	// svn move does not require a log message, since it does not commit.
+	// In LyX we commit immediately afterwards, otherwise it could be
+	// confusing to the user to have two uncommitted files.
+	FileName path(owner_->filePath());
+	string relFile(to_utf8(newFile.relPath(path.absFileName())));
+	string cmd("svn move -q " + quoteName(onlyFileName(owner_->absFileName())) +
+	           ' ' + quoteName(relFile));
+	if (doVCCommand(cmd, path))
+		return string();
+	vector<support::FileName> f;
+	f.push_back(owner_->fileName());
+	f.push_back(newFile);
+	return checkIn(f, msg);
+}
+
+
+string SVN::copy(support::FileName const & newFile, string const & msg)
+{
+	// svn copy does not require a log message, since it does not commit.
+	// In LyX we commit immediately afterwards, otherwise it could be
+	// confusing to the user to have an uncommitted file.
+	FileName path(owner_->filePath());
+	string relFile(to_utf8(newFile.relPath(path.absFileName())));
+	string cmd("svn copy -q " + quoteName(onlyFileName(owner_->absFileName())) +
+	           ' ' + quoteName(relFile));
+	if (doVCCommand(cmd, path))
+		return string();
+	vector<support::FileName> f(1, newFile);
+	return checkIn(f, msg);
+}
+
+
 string SVN::checkIn(string const & msg)
 {
+	vector<support::FileName> f(1, owner_->fileName());
+	return checkIn(f, msg);
+}
+
+
+string SVN::checkIn(vector<support::FileName> const & f, string const & msg)
+{
 	FileName tmpf = FileName::tempName("lyxvcout");
 	if (tmpf.empty()){
 		LYXERR(Debug::LYXVC, "Could not generate logfile " << tmpf);
 		return N_("Error: Could not generate logfile.");
 	}
 
-	doVCCommand("svn commit -m \"" + msg + "\" "
-		    + quoteName(onlyFileName(owner_->absFileName()))
-		    + " > " + quoteName(tmpf.toFilesystemEncoding()),
-		    FileName(owner_->filePath()));
+	ostringstream os;
+	os << "svn commit -m \"" + msg + "\"";
+	for (size_t i = 0; i < f.size(); ++i)
+		os << ' ' << quoteName(f[i].onlyFileName());
+	os << " > " + quoteName(tmpf.toFilesystemEncoding());
+	doVCCommand(os.str(), FileName(owner_->filePath()));
 
 	string log;
 	string res = scanLogFile(tmpf, log);
diff --git a/src/VCBackend.h b/src/VCBackend.h
index 8f2ad41..0baf317 100644
--- a/src/VCBackend.h
+++ b/src/VCBackend.h
@@ -16,6 +16,7 @@
 #include "support/FileName.h"
 
 #include <string>
+#include <vector>
 
 #include "LyXVC.h"
 
@@ -31,7 +32,10 @@ public:
 	enum VCStatus {
 		UNLOCKED,
 		LOCKED,
-		NOLOCKING
+		NOLOCKING,
+		/// This file is not in version control, but it could be aded
+		/// (because the path is under version control)
+		UNVERSIONED,
 	};
 
 	VCS(Buffer * b) : owner_(b) {}
@@ -39,6 +43,10 @@ public:
 
 	/// register a file for version control
 	virtual void registrer(std::string const & msg) = 0;
+	/// rename a file
+	virtual std::string rename(support::FileName const &, std::string const &) = 0;
+	/// copy a file
+	virtual std::string copy(support::FileName const &, std::string const &) = 0;
 	/// check in the current revision, returns log
 	virtual std::string checkIn(std::string const & msg) = 0;
 	/// can this operation be processed in the current VCS?
@@ -140,6 +148,10 @@ public:
 
 	virtual void registrer(std::string const & msg);
 
+	virtual std::string rename(support::FileName const &, std::string const &);
+
+	virtual std::string copy(support::FileName const &, std::string const &);
+
 	virtual std::string checkIn(std::string const & msg);
 
 	virtual bool checkInEnabled();
@@ -214,6 +226,10 @@ public:
 
 	virtual void registrer(std::string const & msg);
 
+	virtual std::string rename(support::FileName const &, std::string const &);
+
+	virtual std::string copy(support::FileName const &, std::string const &);
+
 	virtual std::string checkIn(std::string const & msg);
 
 	virtual bool checkInEnabled();
@@ -343,6 +359,10 @@ public:
 
 	virtual void registrer(std::string const & msg);
 
+	virtual std::string rename(support::FileName const &, std::string const &);
+
+	virtual std::string copy(support::FileName const &, std::string const &);
+
 	virtual std::string checkIn(std::string const & msg);
 
 	virtual bool checkInEnabled();
@@ -393,6 +413,8 @@ protected:
 	bool isLocked() const;
 	/// acquire/release write lock for the current file
 	void fileLock(bool lock, support::FileName const & tmpf, std::string & status);
+	/// Check in files \p f with log \p msg
+	std::string checkIn(std::vector<support::FileName> const & f, std::string const & msg);
 
 private:
 	/// is the loaded file under locking policy?
diff --git a/src/frontends/qt4/GuiView.cpp b/src/frontends/qt4/GuiView.cpp
index 6b9d00d..46758d1 100644
--- a/src/frontends/qt4/GuiView.cpp
+++ b/src/frontends/qt4/GuiView.cpp
@@ -1804,6 +1804,10 @@ bool GuiView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
 	case LFUN_VC_REGISTER:
 		enable = doc_buffer && !doc_buffer->lyxvc().inUse();
 		break;
+	case LFUN_VC_RENAME:
+	case LFUN_VC_COPY:
+		enable = doc_buffer && doc_buffer->lyxvc().inUse();
+		break;
 	case LFUN_VC_CHECK_IN:
 		enable = doc_buffer && doc_buffer->lyxvc().checkInEnabled();
 		break;
@@ -2209,7 +2213,7 @@ void GuiView::insertLyXFile(docstring const & fname)
 }
 
 
-bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
+bool GuiView::renameBuffer(Buffer & b, docstring const & newname, RenameKind kind)
 {
 	FileName fname = b.fileName();
 	FileName const oldname = fname;
@@ -2255,7 +2259,7 @@ bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
 	// trying to overwrite ourselves, which is fine.)
 	if (theBufferList().exists(fname) && fname != oldname
 		  && theBufferList().getBuffer(fname) != &b) {
-		docstring const text = 
+		docstring const text =
 			bformat(_("The file\n%1$s\nis already open in your current session.\n"
 		            "Please close it before attempting to overwrite it.\n"
 		            "Do you want to choose a new filename?"),
@@ -2263,27 +2267,74 @@ bool GuiView::renameBuffer(Buffer & b, docstring const & newname)
 		int const ret = Alert::prompt(_("Chosen File Already Open"),
 			text, 0, 1, _("&Rename"), _("&Cancel"));
 		switch (ret) {
-		case 0: return renameBuffer(b, docstring());
+		case 0: return renameBuffer(b, docstring(), kind);
 		case 1: return false;
 		}
 		//return false;
 	}
-	
-	if (FileName(fname).exists()) {
+
+	bool const existsLocal = fname.exists();
+	bool const existsInVC = LyXVC::fileInVC(fname);
+	if (existsLocal || existsInVC) {
 		docstring const file = makeDisplayPath(fname.absFileName(), 30);
-		docstring const text = bformat(_("The document %1$s already "
-					   "exists.\n\nDo you want to "
-					   "overwrite that document?"),
-					 file);
-		int const ret = Alert::prompt(_("Overwrite document?"),
-			text, 0, 2, _("&Overwrite"), _("&Rename"), _("&Cancel"));
-		switch (ret) {
-		case 0: break;
-		case 1: return renameBuffer(b, docstring());
-		case 2: return false;
+		if (kind != LV_WRITE_AS && existsInVC) {
+			// renaming to a name that is already in VC
+			// would not work
+			docstring text = bformat(_("The document %1$s "
+					"is already registered.\n\n"
+					"Do you want to choose a new name?"),
+				file);
+			docstring const title = (kind == LV_VC_RENAME) ?
+				_("Rename document?") : _("Copy document?");
+			docstring const button = (kind == LV_VC_RENAME) ?
+				_("&Rename") : _("&Copy");
+			int const ret = Alert::prompt(title, text, 0, 1,
+				button, _("&Cancel"));
+			switch (ret) {
+			case 0: return renameBuffer(b, docstring(), kind);
+			case 1: return false;
+			}
+		}
+
+		if (existsLocal) {
+			docstring text = bformat(_("The document %1$s "
+					"already exists.\n\n"
+					"Do you want to overwrite that document?"),
+				file);
+			int const ret = Alert::prompt(_("Overwrite document?"),
+					text, 0, 2, _("&Overwrite"),
+					_("&Rename"), _("&Cancel"));
+			switch (ret) {
+			case 0: break;
+			case 1: return renameBuffer(b, docstring(), kind);
+			case 2: return false;
+			}
 		}
 	}
 
+	switch (kind) {
+	case LV_VC_RENAME: {
+		string msg = b.lyxvc().rename(fname);
+		if (msg.empty())
+			return false;
+		message(from_utf8(msg));
+		break;
+	}
+	case LV_VC_COPY: {
+		string msg = b.lyxvc().copy(fname);
+		if (msg.empty())
+			return false;
+		message(from_utf8(msg));
+		break;
+	}
+	case LV_WRITE_AS:
+		break;
+	}
+	// LyXVC created the file already in case of LV_VC_RENAME or
+	// LV_VC_COPY, but call saveBuffer() nevertheless to get
+	// relative paths of included stuff right if we moved e.g. from
+	// /a/b.lyx to /a/c/b.lyx.
+
 	bool const saved = saveBuffer(b, fname);
 	if (saved)
 		b.reload(false);
@@ -2366,7 +2417,8 @@ bool GuiView::exportBufferAs(Buffer & b)
 }
 
 
-bool GuiView::saveBuffer(Buffer & b) {
+bool GuiView::saveBuffer(Buffer & b)
+{
 	return saveBuffer(b, FileName());
 }
 
@@ -2377,7 +2429,7 @@ bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
 		return true;
 
 	if (fn.empty() && b.isUnnamed())
-			return renameBuffer(b, docstring());
+		return renameBuffer(b, docstring());
 
 	bool success;
 	if (fn.empty())
@@ -2411,7 +2463,7 @@ bool GuiView::saveBuffer(Buffer & b, FileName const & fn)
 		return false;
 	}
 
-	return saveBuffer(b);
+	return saveBuffer(b, fn);
 }
 
 
@@ -2808,6 +2860,18 @@ void GuiView::dispatchVC(FuncRequest const & cmd, DispatchResult & dr)
 		}
 		break;
 
+	case LFUN_VC_RENAME:
+	case LFUN_VC_COPY: {
+		if (!buffer || !ensureBufferClean(buffer))
+			break;
+		if (buffer->lyxvc().inUse() && !buffer->isReadonly()) {
+			RenameKind const kind = (cmd.action() == LFUN_VC_RENAME) ?
+				LV_VC_RENAME : LV_VC_COPY;
+			renameBuffer(*buffer, cmd.argument(), kind);
+		}
+		break;
+	}
+
 	case LFUN_VC_CHECK_IN:
 		if (!buffer || !ensureBufferClean(buffer))
 			break;
@@ -3599,6 +3663,8 @@ void GuiView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 			break;
 
 		case LFUN_VC_REGISTER:
+		case LFUN_VC_RENAME:
+		case LFUN_VC_COPY:
 		case LFUN_VC_CHECK_IN:
 		case LFUN_VC_CHECK_OUT:
 		case LFUN_VC_REPO_UPDATE:
diff --git a/src/frontends/qt4/GuiView.h b/src/frontends/qt4/GuiView.h
index dff2fb6..aa03b84 100644
--- a/src/frontends/qt4/GuiView.h
+++ b/src/frontends/qt4/GuiView.h
@@ -355,6 +355,8 @@ private:
 	///
 	bool exportBufferAs(Buffer & b);
 
+	///
+	enum RenameKind { LV_WRITE_AS, LV_VC_RENAME, LV_VC_COPY };
 	/// Save a buffer as a new file. 
 	/**
 	Write a buffer to a new file name and rename the buffer
@@ -369,8 +371,14 @@ private:
     If 'newname' is non-empty and has an absolute path, that is used.
     Otherwise the base directory of the buffer is used as the base
     for any relative path in 'newname'.
-	*/
-	bool renameBuffer(Buffer & b, docstring const & newname);
+
+	 \p kind controls what is done besides the pure renaming:
+         * LV_WRITE_AS  => The buffer is written without version control actions.
+         * LV_VC_RENAME => The file is renamed in version control.
+         * LV_VC_COPY   => The file is copied in version control.
+	 */
+	bool renameBuffer(Buffer & b, docstring const & newname,
+	                  RenameKind kind = LV_WRITE_AS);
 	///
 	bool saveBuffer(Buffer & b);
 	/// save and rename buffer to fn. If fn is empty, the buffer

Reply via email to