/*
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 1, or (at your option)
** any later version.

** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.

** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

/*
 * Author : Alexandre Parenteau <aubonbeurre@hotmail.com> --- December 1997
 */

/*
 * CvsCommands.cpp --- set of CVS commands
 */

#include "stdafx.h"

#include <errno.h>

#ifdef WIN32
#	include <process.h>
#	include <io.h>
#	include "wincvs.h"

#	ifdef _DEBUG
#	define new DEBUG_NEW
#	undef THIS_FILE
	static char THIS_FILE[] = __FILE__;
#	endif
#endif /* WIN32 */

#include "CvsCommands.h"
#include "Authen.h"
#include "CvsArgs.h"
#include "MultiFiles.h"
#include "PromptFiles.h"
#include "AppConsole.h"
#include "AppGlue.h"
#include "MoveToTrash.h"
#include "LineCmd.h"
#include "CvsAlert.h"
#include "CommitDlg.h"
#include "ImportDlg.h"
#include "CheckoutDlg.h"
#include "TclGlue.h"
#include "ImportFilterDlg.h"
#include "UpdateDlg.h"
#include "LogDlg.h"
#include "DiffDlg.h"
#include "LogParse.h"
#include "RtagDlg.h"
#include "TagDlg.h"
#include "InitDlg.h"
#include "ReleaseDlg.h"
#include "AnnotateDlg.h"
#include "StatusDlg.h"
#include "LoginDlg.h"
#include "BrowseViewHandlers.h"
#include "LaunchHandlers.h"
	
#ifdef qMacCvsPP
#	include "AppGlue.mac.h"
#	include "MacMisc.h"
#	include "MacCvsApp.h"
#	include "GUSIFileSpec.h"
# include <LaunchServices/LaunchServices.h>
#endif

#ifdef qUnix
#include "UCvsApp.h"
#endif

/*!
	Clean up the log message for the server and put the good line feeds
	\param msg Message to be cleaned
	\return The cleaned message
	\todo mac : ISO conversion
*/
char* CleanupLogMsg(const char* msg)
{
	static CStaticAllocT<char> buf;

	size_t len = strlen(msg);
	buf.AdjustSize(len + 1);

	char* tmp = buf;
	char c;


	while( (c = *msg++) != '\0' )
	{
#ifdef WIN32
		if(c == '\r')
			continue;
#endif /* WIN32 */
#ifdef TARGET_OS_MAC
		if(c == '\r')
			c = '\n';
#endif /* TARGET_OS_MAC */
		*tmp++ = c;
	}

	*tmp++ = '\0';

	return buf;
}

/*!
	Split the path into the up-directory and the folder
	\param dir Path to be split
	\param uppath Return up-directory
	\param folder Return folder or file
	\param dontSkipDelimiter Set to true to prevent skipping the delimiter if the path to be split ends with one
	\return true on success, false otherwise
	\note Passing NULL is considered an error, passing empty string returns true
*/
bool SplitPath(const char* dir, CStr& uppath, CStr& folder, bool dontSkipDelimiter /*= false*/)
{
	char newDir[MAX_PATH * 2] = "";
	char oldDir[MAX_PATH * 2] = "";
	char newFile[MAX_PATH] = "";

	if( !dir )
	{
		// Null pointer
		return false;
	}

	int dirLen = strlen(dir);
	if( dirLen > 0 )
	{
		strcpy(oldDir, dir);
		
		if( !dontSkipDelimiter && (dir[dirLen - 1] == kPathDelimiter) )
		{
			oldDir[dirLen - 1] = '\0';
			--dirLen;
		}
		
		const char* tmp = oldDir;
		const char* prev = oldDir;
		
		while( (tmp = strchr(tmp, kPathDelimiter)) != 0L )
		{
			prev = ++tmp;
		}
		
		strcpy(newDir, oldDir);
		newDir[prev - oldDir] = '\0';
		strcpy(newFile, prev);
	}
		
	uppath = newDir;
	folder = newFile;

	return true;
}

/*!
	Find a unique name for a temporary file
	\param file Return temporary file name
	\param prefix Prefix of the filename
	\param extension Extension of the filename
	\param create Set to true to create the file
	\return true on success, false otherwise
*/
bool MakeTmpFile(CStr& file, const char* prefix, const char* extension /*= 0L*/, bool create /*= false*/)
{
	static int count = 0;
	char filename[MAX_PATH];

	CStr tmpPath;
	CStr ext;
	CStr prf;

	if( extension != 0L && extension[0] != '\0' )
	{
		if( extension[0] != '.' )
			ext = ".";

		ext << extension;
	}

	if( prefix != 0L && prefix[0] != '\0' )
	{
		prf = prefix;
	}
	else
	{
		prf = "cvsgui";
	}

#ifdef WIN32
	char* ptr;
	ptr = getenv(_T("TMP"));
	if( ptr != 0L && _access(ptr, 0) != -1 )
	{
		tmpPath = ptr;
	}
	else
	{
		tmpPath = ".";
	}
#endif /* !WIN32 */
#ifdef TARGET_OS_MAC
	UFSSpec theFolder;
	OSErr err;
	
	if( (err = MacGetTempFolder(theFolder, tmpPath)) != noErr )
	{
		cvs_err(_i18n("Unable to locate the preferences folder (error %d) !\n"), err);
		return 0L;
	}
#endif /* !TARGET_OS_MAC */
#if qUnix
	tmpPath = "/tmp";
#endif

	struct stat sb;
	if( stat(tmpPath, &sb) == -1 || !S_ISDIR(sb.st_mode) )
	{
		cvs_err(_i18n("Unable to access '%s' (error %d) !\n"), (const char*)tmpPath, errno);
		return false;
	}

	if( !tmpPath.endsWith(kPathDelimiter) )
		tmpPath << kPathDelimiter;

	int maxtry = 10000;
	while( true )
	{
		sprintf(filename, "%s%d%s", (const char*)prf, count++, (const char*)ext);
		file = tmpPath;
		file << filename;

		if( stat(file, &sb) == -1 && errno == ENOENT )
		{
			if( create )
			{
				FILE* stream = fopen(file, "w");
				if( stream )
				{
					fclose(stream);
				}
			}
		
			return true;
		}

		if( maxtry-- == 0 )
		{
			cvs_err(_i18n("Time out to access '%s' !\n"), (const char*)tmpPath);
			break;
		}
	}

	return false;
}

/*!
	Extract the extension from the filename
	\param file File name to extract extension from
	\param base Return the base of the filename
	\param ext Return the file's extension
*/
void GetExtension(const char* file, CStr& base, CStr& ext)
{
	char* tmp = strrchr(file, '.');
	
	if( tmp == 0L )
	{
		ext = "";
		base = file;
	}
	else
	{
		ext = tmp + 1;
		base = file;
		base[tmp - file] = '\0';
	}
}

/*!
	Parse a command like 'user=whohost=cvs'
	\param cmd Command
	\param key Key
	\param value Return value
	\return	true on success, false otherwise
*/
bool GetEnvValue(const char* cmd, const char* key, UStr& value)
{
	const char* tmp = strstr(cmd, key);
	if( tmp == 0L )
		return false;

	tmp = strchr(tmp, '=');
	if( tmp == 0L )
		return false;

	tmp++;

	const char* tmp2 = strchr(tmp, '&');
	if( tmp2 == 0L )
		value = tmp;
	else
		value.set(tmp, tmp2 - tmp);

	return true;
}

/*!
	Convert a command line (possibly quoted) into regular argc, argv arguments
	\param cmdLine Command line
	\param argv Return argv arguments
	\return	The count of arguments (argc)
*/
int StringToArgv(const char* cmdLine, char** argv)
{
	int argc = 0;
	
	char Quote = '\0';				/*	Contains the quote character if we are inside "" or '', or \0 otherwise */ 
	const char*	walker1 = cmdLine;	/*	walker1 traverses the original command line */
	char* walker2;					/*	walker2 traverses the parsed copy of the commandline */
	char theChar;					/*	theChar is the character we are currently parsing */
	char arguments[MAX_CMD_LEN];	/*	Will contain a parsed copy of the command line */
	
	/* Copy into arguments */
	walker2 = arguments;
	
	/* Parse command line to \0 */										
	while( (theChar = *walker1++) != '\0' ) 
	{
		/* Skip leading space before an argument */					
		if( theChar == ' ' )
			continue;
		
		/* Set argv to point to the space where we copy the next argument */
		argv [argc++] = walker2;								
		
		/* If we filled argv, skip the rest */															
		if( argc > MAX_ARGS)
		{
			argc--;
			break;
		}

		do
		{
			/* Parse an argument skip \s */
			if( theChar == '\\' && *walker1++ )
			{
				theChar = *walker1++;
			}
			else
			{
				/* When we find a quote... */
				if( theChar == '"' || theChar == '\'' )  
				{
					/* If we are not inside quotes already, */
					if( Quote == '\0' )
					{
						/* Remember we are inside quotes now */						
						Quote = theChar;
						
						/* Go to the next character */						
						continue;
					}
					
					/* If we are already inside that kind of quotes */
					if( theChar == Quote )
					{
						/* Close quotes */
						Quote = '\0';							
						
						/* Go to the next character */						
						continue;
					}
				}
			}
			
			/* Copy the character into args */
			*walker2++ = theChar;								
																
			/* Repeat until we reach the end of arguments or we reach a space that is not inside quotes */															
		}while( (*walker1 != '\0') && (((theChar = *walker1++) != ' ') || (Quote != '\0')) );
		
		/* Null-terminate the last argument */
		*walker2++ = 0;											
	}

	return argc;
}

#if defined(WIN32) || defined(qUnix)
/*!
	Removes folder separators by replacing them with dots so that temporary file 
	could be created without creating matching subfolders in the temporary file folder
	\param tmpf File with subfolder
	\return true on success, false otherwise
	\note Windows client may provide file names pointing into subfolders
*/
static void FlatenSubFolders(CStr& tmpf)
{
	char* cp = (char*)tmpf;
	
	if( cp )
	{
		while( *cp )
		{
			if( *cp == kPathDelimiter )
			{
				*cp = '.';
			}

			cp++;
		}
	}	
}
#elif TARGET_OS_MAC
static void FlatenSubFolders(CStr& tmpf)
{
	char *tmp;
	if((tmp = strrchr(tmpf, kPathDelimiter)) != NULL)
	{
		UStr newStr(++tmp);
		tmpf = newStr;
	}
}
#else
#define FlatenSubFolders(tmpf) // no-op on other platforms
#endif

/*!
	Execute admin macro
	\param macroName Macro name to be executed
*/
void CvsCmdMacrosAdmin(const char* macroName)
{
	if( gCvsPrefs.empty() || !CTcl_Interp::IsAvail() )
		return;
	
	CTcl_Interp interp;
	CStr path = macroName;
	CTcl_Interp::Unixfy(path);
	interp.DoScriptVar("source \"%s\"", (const char*)path);
}

/*!
	Verify the selection handler is properly setup to carry on launching command
	\param handler Command handler to be tested
	\param mf Returned selection as MultiFiles
	\param forceNormalize specifies whether to force normalization of the MultiFiles or rely on the selection handler's requirements
	\return true if handler is properly setup, false otherwise
*/
static bool CheckSelectionHandler(KiSelectionHandler& handler, MultiFiles*& mf, bool forceNormalize=false)
{
	if( gCvsPrefs.empty() )
	{
		return false;
	}

	mf = handler.GetSelection();
	if( !mf && handler.GetNeedSelection() )
	{
		return false;
	}

	return ( forceNormalize || handler.NeedsNormalize() ) ? mf->Normalize() : true;
}

/*!
	Execute <b>cvs init</b> command
*/
void CvsCmdInit()
{
	if( gCvsPrefs.empty() )
		return;
	
	bool forceCvsroot;

	if( !CompatGetInit(forceCvsroot) )
		return;

	CvsArgs args;

	if( forceCvsroot )
	{
		args.addcvsroot();
	}

	args.add("init");
	
	args.print();
	launchCVS(0L, args.Argc(), args.Argv());
}

/*!
	Execute <b>cvs login</b> command
*/
void CvsCmdLogin(void)
{
	if( gCvsPrefs.empty() )
		return;
	
	bool forceCvsroot;

	if( !CompatGetLogin(forceCvsroot) )
		return;

	AuthenModel* curModel = AuthenModel::GetInstance(gAuthen.kind());
	if( !curModel->HasLogin() )
	{
		cvs_err(_i18n("Logging in is required for 'pserver' or 'sspi' authentication only (see Preferences dialog).\n"
			"Please consult the CVS manual for more details.\n"));
	}
	
	if( gCvsPrefs.HasLoogedIn() && gCvsPrefs.LogoutTimeOut() > 0 )
	{
		cvs_err(_i18n("Making automatic logout:\n"));
		CvsCmdLogout();
		WaitForCvs();
	}
	
	gCvsPrefs.SetHasLoogedIn(true);

	CvsArgs args;

	if( forceCvsroot )
	{
		args.addcvsroot();
	}

	args.add("login");

	args.print();
	launchCVS(0L, args.Argc(), args.Argv());
}

/*!
	Execute <b>cvs logout</b> command
*/
void CvsCmdLogout(void)
{
	if( gCvsPrefs.empty() )
		return;
	
	AuthenModel* curModel = AuthenModel::GetInstance(gAuthen.kind());
	if( !curModel->HasLogin() )
	{
		cvs_err(_i18n("Logging out is required for 'pserver' or 'sspi' authentication only (see Preferences dialog).\n"
			"Please consult the CVS manual for more details.\n"));
	}
	
	gCvsPrefs.SetHasLoogedIn(false);

	CvsArgs args;

	args.add("logout");

	args.print();
	launchCVS(0L, args.Argc(), args.Argv());
}

/*!
	Execute <b>cvs rtag</b> command
	\param handler Selection for the command
*/
void CvsCmdRtagCreate(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool norecurs;
	bool movDelBranch;
	bool overwriteExisting;
	bool useMostRecent;
	bool lookAttic;
	CPStr modname;
	CPStr tagname;
	CPStr date;
	CPStr rev;
	bool forceCvsroot;

	if( !CompatRtagCreate(mf, norecurs, movDelBranch, overwriteExisting, tagname, modname, date, rev, useMostRecent, lookAttic, forceCvsroot) )
		return;

	CvsArgs args;

	if( forceCvsroot )
	{
		args.addcvsroot();
	}

	args.add("rtag");
	
	if( norecurs )
		args.add("-l");
	
	if( movDelBranch )
		args.add("-B");
	
	if( useMostRecent )
		args.add("-f");
	
	if( overwriteExisting )
		args.add("-F");
	
	if( lookAttic )
		args.add("-a");
	
	if( !date.empty() )
	{
		args.add("-D");
		args.add(date);
	}
	
	if( !rev.empty() )
	{
		args.add("-r");
		args.add(rev);
	}

	args.add(tagname);
	args.add(modname);
	
	args.print();
	launchCVS(0L, args.Argc(), args.Argv());
}

/*!
	Execute <b>cvs rtag -d</b> command
	\param handler Selection for the command
*/
void CvsCmdRtagDelete(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool norecurs;
	bool movDelBranch;
	bool lookAttic;
	CPStr modname;
	CPStr tagname;
	bool forceCvsroot;

	if( !CompatRtagDelete(mf, norecurs, movDelBranch, lookAttic, tagname, modname, forceCvsroot) )
		return;

	CvsArgs args;

	if( forceCvsroot )
	{
		args.addcvsroot();
	}

	args.add("rtag");
	args.add("-d");
	
	if( norecurs )
		args.add("-l");
	
	if( movDelBranch )
		args.add("-B");
	
	if( lookAttic )
		args.add("-a");

	args.add(tagname);
	args.add(modname);
	
	args.print();
	launchCVS(0L, args.Argc(), args.Argv());
}

/*!
	Execute <b>cvs rtag -b</b> command
	\param handler Selection for the command
*/
void CvsCmdRtagBranch(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool norecurs;
	bool movDelBranch;
	bool overwriteExisting;
	bool useMostRecent;
	bool lookAttic;
	CPStr modname;
	CPStr branchname;
	CPStr date;
	CPStr rev;
	bool forceCvsroot;

	if( !CompatRtagBranch(mf, norecurs, movDelBranch, overwriteExisting, branchname, modname, date, rev, useMostRecent, lookAttic, forceCvsroot) )
		return;

	CvsArgs args;

	if( forceCvsroot )
	{
		args.addcvsroot();
	}

	args.add("rtag");
	args.add("-b");
	
	if( norecurs )
		args.add("-l");
	
	if( movDelBranch )
		args.add("-B");
	
	if( overwriteExisting )
		args.add("-F");
	
	if( useMostRecent )
		args.add("-f");
	
	if( lookAttic )
		args.add("-a");
	
	if( !date.empty() )
	{
		args.add("-D");
		args.add(date);
	}
	
	if( !rev.empty() )
	{
		args.add("-r");
		args.add(rev);
	}

	args.add(branchname);
	args.add(modname);
	
	args.print();
	launchCVS(0L, args.Argc(), args.Argv());
}

/*!
	Execute <b>cvs checkout</b> and <b>cvs export</b> command
	\param handler Selection for the command
*/
void CvsCmdCheckoutModule(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	MultiFiles::const_iterator mfi = mf->begin();
	const char* dir = mfi->getdir();
	bool norecurs;
	CPStr modname;
	CPStr path(dir);
	bool toStdout;
	CPStr date;
	CPStr rev;
	bool useMostRecent;
	CPStr rev1;
	CPStr rev2;
	bool doexport;
	bool forceCvsroot;
	bool overrideCheckoutDir;
	CPStr checkoutDir;
	CPStr keyword;
	bool resetSticky;
	bool dontShortenPaths;

	if( !CompatGetCheckout(mf, modname, path, norecurs,
		toStdout, date, rev, useMostRecent, rev1, rev2, doexport, forceCvsroot,
		overrideCheckoutDir, checkoutDir, keyword, resetSticky, dontShortenPaths) )
	{
		return;
	}

	CvsArgs args;

	if( forceCvsroot )
	{
		args.addcvsroot();
	}

	args.add(doexport ? "export" : "checkout");

	if( gCvsPrefs.PruneOption() && !doexport )
		args.add("-P");
	
	if( norecurs )
		args.add("-l");
	
	if( toStdout )
		args.add("-p");
	
	if( resetSticky )
	{
		args.add("-A");
	}
	else
	{
		if( !keyword.empty() )
		{
			args.add(keyword);
		}
		
		if( useMostRecent )
		{
			args.add("-f");
		}
		
		if( !date.empty() )
		{
			args.add("-D");
			args.add(date);
		}
		
		if( !rev.empty() )
		{
			args.add("-r");
			args.add(rev);
		}
	}

	if( !rev1.empty() )
	{
		CPStr tmp;
		tmp = "-j";
		tmp << rev1;
		args.add(tmp);
	}
	
	if( !rev2.empty() )
	{
		CPStr tmp;
		tmp = "-j";
		tmp << rev2;
		args.add(tmp);
	}
	
	if( doexport && rev.empty() && date.empty() )
	{
		args.add("-r");
		args.add("HEAD");
	}

	if( overrideCheckoutDir )
	{
		if( dontShortenPaths )
			args.add("-N");

		args.add("-d");
		args.add(checkoutDir);
	}

	args.add(modname);
	
	args.print(path);
	launchCVS(path, args.Argc(), args.Argv());
}

/*!
	Execute <b>cvs import</b> command
	\param handler Selection for the command
*/
void CvsCmdImportModule(KiSelectionHandler& handler)
{	
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		ReportConflict* entries = NULL;
		ReportWarning* warnings = NULL;
		const char* dir = mfi->getdir();
		
		if( !CompatImportFilter(dir, entries, warnings) )
			return;
		
		CPStr modname, vendortag, reltag, path(dir), vendorBranchId;
		CStr logmsg;
		bool useDefIgnore;
		bool useFilesTime;
		bool createSandbox;
		bool dontCreateVendorTag;

		if( !CompatGetImport(mf, modname, logmsg, vendortag, reltag, vendorBranchId, path, useDefIgnore, useFilesTime, createSandbox, dontCreateVendorTag) )
			return;
		
		CvsArgs args;
		args.add("import");
		
		if( createSandbox )
		{
			args.add("-C");
		}

		if( useFilesTime )
		{
			args.add("-d");
		}
		
		// fill the wrappers
		ReportConflict* tmpentry = entries;
		
		if( !useDefIgnore )
		{
			args.add("-I"); // no default (core, *.o...)
			args.add("!");
			args.add("-I"); // but ignore the CVS folders
			args.add("CVS");
		}
		
		while( tmpentry != 0L )
		{
			if( tmpentry->HasIgnore() )
			{
				CPStr ign;
				args.add("-I");
				
				if( tmpentry->IsExtension() )
					ign << '*';
				
				ign << tmpentry->GetPattern();
				args.add(ign);
			}
			else if( !tmpentry->HasForceText() &&
				(tmpentry->IsBinary() || tmpentry->HasForceBinary() || tmpentry->HasForceNoKeywords() || 
				tmpentry->IsUnicode() || tmpentry->HasForceUnicode()) )
			{
				CPStr wrap;
				const char* pattern = tmpentry->GetPattern();
				bool hasSpace = strchr(pattern, ' ') != 0L;
				args.add("-W");
				
				if( hasSpace )
					wrap << '"';
				
				if( tmpentry->IsExtension() )
					wrap << '*';
				
				wrap << pattern;
				
				if( hasSpace )
					wrap << '"';
				
				if( tmpentry->HasForceNoKeywords() )
				{
					wrap << " -k 'o'";
				}
				else if( tmpentry->HasForceBinary() )
				{
					wrap << " -k 'b'";
				}
				else if( tmpentry->HasForceUnicode() )
				{
					wrap << " -k 'u'";
				}
				else if( tmpentry->IsBinary() )
				{
					wrap << " -k 'b'";
				}
				else if( tmpentry->IsUnicode() )
				{
					wrap << " -k 'u'";
				}
				
				args.add(wrap);
			}
			
			tmpentry = tmpentry->next;
		}
		
		free_list_types(warnings, entries);
		
		if( !vendorBranchId.empty() )
		{
			args.add("-b");
			args.add(vendorBranchId);
		}

		args.add("-m");
		args.add(CleanupLogMsg(logmsg));
		
		if( dontCreateVendorTag )
		{
			args.add("-n");
			args.add(modname);
		}
		else
		{
			args.add(modname);
			args.add(vendortag);
			args.add(reltag);
		}

		args.print(path);
		launchCVS(path, args.Argc(), args.Argv());
	}
}

/*!
	Execute command line
	\param handler Selection for the command
*/
void CvsCmdCommandLine(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	CStr dir;
	CStr cmd;
	bool addDefault;
	bool addSelection;
	bool forceCvsroot;

	if( !CompatGetCommand(mf, cmd, dir, addDefault, addSelection, forceCvsroot) )
		return;

	char* argv[MAX_ARGS];
	int argc = StringToArgv(cmd.c_str(), argv);
	
	if( addSelection )
	{
		MultiFiles::const_iterator mfi;
		for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
		{
			CvsArgs args(addDefault);
			
			if( addDefault && forceCvsroot )
			{
				args.addcvsroot();
			}

			for(int i = 0; i < argc; i++)
			{
				if( addDefault && i == 0 && strcmp(argv[i], CVS_CMD) == 0 )
				{
					// Skip the "cvs" arguments as it's already added by args constructor
					continue;
				}

				args.add(argv[i]);
				
				if( !addDefault && forceCvsroot && i == 0 && strcmp(argv[i], CVS_CMD) == 0 )
				{
					args.addcvsroot();
				}
			}
			
			const char* dir = mfi->add(args);
			args.print(dir);
			launchCVS(dir, args.Argc(), args.Argv());
		}
	}
	else
	{
		CvsArgs args(addDefault);
		
		if( addDefault && forceCvsroot )
		{
			args.addcvsroot();
		}

		for(int i = 0; i < argc; i++)
		{
			if( addDefault && i == 0 && strcmp(argv[i], CVS_CMD) == 0 )
			{
				// Skip the "cvs" arguments as it's already added by args constructor
				continue;
			}
			
			args.add(argv[i]);
			
			if( !addDefault && forceCvsroot && i == 0 && strcmp(argv[i], CVS_CMD) == 0 )
			{
				args.addcvsroot();
			}
		}
		
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs update</b> command
	\param handler Selection for the command
*/
void CvsCmdCancelChanges(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		RemoveCvsArgs args;
		args.add("update");

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs update</b> command
	\param handler Selection for the command
	\param queryOnly true for query update, false for the update command
*/
void CvsCmdUpdate(KiSelectionHandler& handler, bool queryOnly)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool toStdout;
	bool noRecurs;
	bool resetSticky;
	CPStr date;
	CPStr rev;
	bool useMostRecent;
	CPStr rev1;
	CPStr rev2;
	bool createMissDir;
	bool getCleanCopy;
	CPStr keyword;

	if( !queryOnly && !CompatGetUpdate(mf, toStdout, noRecurs, resetSticky,
		date, rev, useMostRecent, rev1, rev2, createMissDir, getCleanCopy, keyword) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;

		if( queryOnly )
			args.add("-n");
		
		args.add("update");

		if( !keyword.empty() )
		{
			args.add(keyword);
		}

		if( gCvsPrefs.PruneOption() )
			args.add("-P");

		if( !queryOnly )
		{
			if( resetSticky )
				args.add("-A");
		
			if( createMissDir )
				args.add("-d");
			
			if( getCleanCopy )
				args.add("-C");
			
			if( !date.empty() )
			{
				args.add("-D");
				args.add(date);
			}
			
			if( useMostRecent )
				args.add("-f");
			
			if( !rev1.empty() )
			{
				CPStr tmp;
				tmp = "-j";
				tmp << rev1;
				args.add(tmp);
			}
			
			if( !rev2.empty() )
			{
				CPStr tmp;
				tmp = "-j";
				tmp << rev2;
				args.add(tmp);
			}
			
			if( noRecurs )
				args.add("-l");
			
			if( toStdout )
				args.add("-p");
			
			if( !rev.empty() )
			{
				args.add("-r");
				args.add(rev);
			}
		}

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs commit</b> command
	\param handler Selection for the command
*/
void CvsCmdCommit(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool norecurs;
	CStr logmsg;
	bool forceCommit;
	bool forceRecurse;
	CPStr rev;
	bool noModuleProgram;
	bool checkValidEdits;	

	if( !CompatGetCommit(mf, logmsg, norecurs, forceCommit, forceRecurse, rev, noModuleProgram, checkValidEdits) )
		return;
	
	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("commit");

		if( checkValidEdits )
			args.add("-c");
		
		if( norecurs )
			args.add("-l");
		
		if( noModuleProgram )
			args.add("-n");
		
		if( forceCommit )
			args.add("-f");
		
		if( forceRecurse )
			args.add("-R");
		
		args.add("-m");
		args.add(CleanupLogMsg(logmsg));
		
		if( !rev.empty() )
		{
			args.add("-r");
			args.add(rev);
		}

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs diff</b> or <b>external diff</b> command
	\param handler Selection for the command
	\param diffType Diff command type
*/
void CvsCmdDiff(KiSelectionHandler& handler, kDiffCmdType diffType)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool noRecurs;
	bool isDate1;
	bool isDate2;
	bool useExtDiff;
	CPStr rev1;
	CPStr rev2;
	bool unifiedDiff;
	bool noWhiteSpace;
	bool noBlankLines;
	bool ignoreCase;
	bool extDiffResult;
	CPStr diffOptions;
	CPStr keyword;

	if( diffType == kDiffGraph )
	{
		// determine initial revision settings
		if( mf->TotalNumFiles() > 0 )
		{
			const MultiFilesEntry& mfe = *mf->begin();
			CStr ignorePath, ignoreName, rev;

			switch( mfe.NumFiles() )
			{
			case 2:
				if( mfe.get(1, ignorePath, ignoreName, rev) ) 
					rev2 = rev;
				
				// fall thru
			case 1:
				if( mfe.get(0, ignorePath, ignoreName, rev) ) 
					rev1 = rev;

				break;
			}
		}
	}
	
	if( !CompatGetDiff(mf, diffType, noRecurs, isDate1, isDate2, rev1, rev2, useExtDiff, 
		unifiedDiff, noWhiteSpace, noBlankLines, ignoreCase, keyword, diffOptions, 
		mf->TotalNumFiles() > 0) )
	{
		return;
	}

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		extDiffResult = false;

		if( mfi->NumFiles() && useExtDiff )
		{
			CStr path, filename, ext, base, currRev;
			CStr file1, file2;

			for(int i = 0; ; i++)
			{
				if( !mfi->get(i, path, filename, currRev) )
					break;

				if( 0 < i && kDiffGraph == diffType )
				{
					// Graph diff contains two files, but it's actually the same one
					break;
				}

				// note that filename can contain subfolders
				
				GetExtension(filename, base, ext);

				CvsArgs args;
				CStr tmpf(base);
				tmpf << '_';
				
				if( !rev1.empty() )
					tmpf << rev1;
				else
					tmpf << currRev;
				
				tmpf << '_';

				// convert subfolder names to regular name
				FlatenSubFolders(tmpf);

				args.add("update");
				args.add("-p");
				
				if( !rev1.empty() )
				{
					args.add(isDate1 ? "-D" : "-r");
					args.add(rev1);
				}
 				else if( !currRev.empty() ) 
				{
 					args.add("-r");
					args.add(currRev);
  				}

				args.add(filename);
				args.print(path);
				file1 = launchCVS(path, args, (const char *)tmpf, ext);

				if( rev2.empty() )
				{
					file2 = path;
					
					if( !file2.endsWith(kPathDelimiter) )
						file2 << kPathDelimiter;
					
					file2 << filename;
				}
				else
				{
					CvsArgs args2;

					tmpf = base;
					tmpf << '_';
					tmpf << rev2;
					tmpf << '_';

					// convert subfolder names to regular name
					FlatenSubFolders(tmpf);

					args2.add("update");
					args2.add("-p");
					args2.add(isDate2 ? "-D" : "-r");
					args2.add(rev2);
					args2.add(filename);
					args2.print(path);
					file2 = launchCVS(path, args2, (const char *)tmpf, ext);
				}

				if( file1.empty() || file2.empty() )
					continue;

				extDiffResult = LaunchDiff(file1, file2);
			}
		}

		if( !mfi->NumFiles() || !useExtDiff || !extDiffResult )
		{
			CvsArgs args;
			args.add("diff");

			if( noRecurs )
				args.add("-l");

			if( !rev1.empty() )
			{
				args.add(isDate1 ? "-D" : "-r");
				args.add(rev1);
			}
			
			if( !rev2.empty() )
			{
				args.add(isDate2 ? "-D" : "-r");
				args.add(rev2);
			}
			
			if( unifiedDiff )
				args.add("-u");	    // --unified, -u

			if( noWhiteSpace )
				args.add("-wb");	//--ignore-all-space --ignore-space-change

			if( noBlankLines )
				args.add("-B");		//--ignore-blank-lines

			if( ignoreCase )
				args.add("-i");		//--ignore-case

			if( !keyword.empty() )
			{
				args.add(keyword);
			}

			if( !diffOptions.empty() )
			{
				args.parse(diffOptions);
			}

			const char* dir = mfi->add(args);
			args.print(dir);
			launchCVS(dir, args.Argc(), args.Argv());
		}
	}
}

/*!
	Execute <b>cvs add</b> command
	\param handler Selection for the command
	\param addType Add type
*/
void CvsCmdAdd(KiSelectionHandler& handler, kAddCmdType addType /*= kAddNormal*/)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		if( mfi->NumFiles() > 0 )
		{
			// Files
			ControlCvsArgs* args = NULL;
			switch( addType )
			{
			default:
			case kAddNormal:
				args = new AddCvsArgs;
				break;
			case kAddBinary:
				args = new AddBinCvsArgs;
				break;
			case kAddUnicode:
				args = new AddUnicodeCvsArgs;
				break;
			}

			if( !args )
			{
				cvs_err("Unable to allocate memory for arguments list\n");
				return;
			}

			args->add("add");
			
			if( addType == kAddBinary )
			{
				args->add("-kb");
			}
			else if( addType == kAddUnicode )
			{
				args->add("-ku");
			}

			const char* dir = mfi->add(*args);
			
			CvsAlert cvsAlert(kCvsAlertWarningIcon, 
				_i18n("Do you want to continue?"), _i18n("Some problems have been found while adding files and should be corrected. See the console window for details."),
				BUTTONTITLE_CONTINUE, BUTTONTITLE_CANCEL);

			if( !args->HasProblem() || cvsAlert.ShowAlert() == kCvsAlertOKButton )
			{
				args->print(dir);
				launchCVS(dir, args->Argc(), args->Argv());
			}

			delete args;
		}
		else
		{
			// Directory
			const char* dir = mfi->getdir();
			CStr uppath, folder;

			if( !SplitPath(dir, uppath, folder) )
			{
				cvs_err(_i18n("Unable to convert path name '%s'\n"), dir);
				return;
			}
			
			CvsArgs args;
			args.add("add");
			args.add(folder);
			
			args.print(uppath);
			launchCVS(uppath, args.Argc(), args.Argv());
		}
	}
}

/*!
	Execute <b>cvs remove</b> command
	\param handler Selection for the command
*/
void CvsCmdRemove(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		RemoveCvsArgs args;
		
		CompatBeginMoveToTrash();
		
		args.add("remove");
		const char* dir = mfi->add(args);
		args.print(dir);
		
		CompatEndMoveToTrash();
		
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs log</b> or <b>graph</b> command
	\param handler Selection for the command
	\param outGraph true for the graph
	\param defWnd Default window
*/
void CvsCmdLog(KiSelectionHandler& handler, bool outGraph, void* defWnd)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool noRecurs;
	bool defBranch;
	CPStr date;
	bool headerOnly;
	bool headerDesc;
	bool supress;
	bool noTags;
	bool onlyRCSfile;
	bool hasRev;
	CPStr rev;
	CPStr states;
	bool hasUser;
	CPStr users;

	if( !CompatGetLog(mf, noRecurs, defBranch, date,
		headerOnly, headerDesc, supress, 
		noTags, onlyRCSfile, hasRev,
		rev, states, hasUser, users,
		outGraph) )
	{
		return;
	}

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("log");

		if( noRecurs )
			args.add("-l");

		if( defBranch )
			args.add("-b");

		if( headerDesc )
		{
			args.add("-t");
		}
		else
		{
			if( headerOnly )
				args.add("-h");
		}

		if( supress )
			args.add("-S");

		if( noTags )
			args.add("-N");

		if( onlyRCSfile )
			args.add("-R");

		if( !date.empty() )
		{
			args.add("-d");
			args.add(date);
		}

		if( !states.empty() )
		{
			args.add("-s");
			args.add(states);
		}

		if( hasRev )
		{
			CPStr tmp;
			tmp = "-r";
			tmp << rev;
			args.add(tmp);
		}

		if( hasUser )
		{
			CPStr tmp;
			tmp = "-w";
			tmp << users;
			args.add(tmp);
		}

		const char* dir = mfi->add(args);
		args.print(dir);
		CvsLogParse(dir, args, outGraph, defWnd);
	}
}

/*!
	Execute <b>cvs status</b> command
	\param handler Selection for the command
*/
void CvsCmdStatus(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool noRecurs = false;
	STATUSOUTPUT_TYPE outputType = STATUSOUTPUT_VERBOSE;

	if( !CompatGetStatus(mf, noRecurs, outputType) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("status");

		if( noRecurs )
			args.add("-l");

		if( STATUSOUTPUT_VERBOSE == outputType )
		{
			args.add("-v");
		}
		else if( STATUSOUTPUT_QUICK == outputType )
		{
			args.add("-q");
		}

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs admin -l</b> command
	\param handler Selection for the command
*/
void CvsCmdLock(KiSelectionHandler& handler)
{	
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("admin");
		args.add("-l");

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs admin -u</b> command
	\param handler Selection for the command
*/
void CvsCmdUnlock(KiSelectionHandler& handler)
{	
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("admin");
		args.add("-u");

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs tag</b> command
	\param handler Selection for the command
*/
void CvsCmdTagCreate(KiSelectionHandler& handler)
{	
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool noRecurs;
	bool movDelBranch;
	bool overwriteExisting;
	bool checkUnmod;
	CPStr tagName;

	if( !CompatTagCreate(mf, noRecurs, movDelBranch, overwriteExisting, tagName, checkUnmod) )
		return;
	
	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("tag");

		if( noRecurs )
			args.add("-l");

		if( movDelBranch )
			args.add("-B");
	
		if( overwriteExisting )
			args.add("-F");

		if( checkUnmod )
			args.add("-c");

		args.add(tagName);

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs tag -d</b> command
	\param handler Selection for the command
*/
void CvsCmdTagDelete(KiSelectionHandler& handler)
{	
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool noRecurs;
	bool movDelBranch;
	CPStr tagName;

	if( !CompatTagDelete(mf, noRecurs, movDelBranch, tagName) )
		return;
	
	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("tag");
		args.add("-d");
		
		if( noRecurs )
			args.add("-l");

		if( movDelBranch )
			args.add("-B");
		
		args.add(tagName);

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs tag -b</b> command
	\param handler Selection for the command
*/
void CvsCmdTagBranch(KiSelectionHandler& handler)
{	
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool noRecurs;
	bool movDelBranch;
	bool overwriteExisting;
	bool checkUnmod;
	CPStr branchName;

	if( !CompatTagBranch(mf, noRecurs, movDelBranch, overwriteExisting, branchName, checkUnmod) )
		return;
	
	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("tag");
		args.add("-b");

		if( noRecurs )
			args.add("-l");

		if( movDelBranch )
			args.add("-B");
		
		if( overwriteExisting )
			args.add("-F");

		if( checkUnmod )
			args.add("-c");

		args.add(branchName);

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs </b> command
	\param handler Selection for the command
	\param editType Edit type
*/
void CvsCmdEdit(KiSelectionHandler& handler, kEditCmdType editType /*= kNormal*/)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("edit");

		switch( editType )
		{
		default:
		case kEditNormal:
			// intentionally blank
			break;
		case kEditReserved:
			args.add("-c");
			break;
		case kEditForce:
			args.add("-f");
			break;
		}
		
		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs unedit</b> command
	\param handler Selection for the command
*/
void CvsCmdUnedit(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("unedit");

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs watch add</b> command
	\param handler Selection for the command
*/
void CvsCmdWatchOn(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("watch");
		args.add("add");

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs watch remove</b> command
	\param handler Selection for the command
*/
void CvsCmdWatchOff(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("watch");
		args.add("remove");

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs release</b> command
	\param handler Selection for the command
*/
void CvsCmdRelease(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	CStr dir;
	if( !mf->getdir(0, dir) )
	{
		return;
	}

	CStr uppath, folder;
	if( !SplitPath(dir, uppath, folder) )
	{
		cvs_err(_i18n("Unable to convert path name '%s'\n"), dir.c_str());
		
		// Even thought something didn't work we will try to continue and put all the responsibility onto the user
		uppath = dir;
		folder = ".";
	}

	kReleaseDelType removeDirectoryOption = kReleaseNoDel;

	if( !CompatGetRelease(mf, dir, removeDirectoryOption) )
	{
		return;
	}

	CvsArgs args;
	args.add("release");
	
	switch( removeDirectoryOption )
	{
	default:
	case kReleaseNoDel:
		break;
	case kReleaseDelDir:
		args.add("-d");
		break;
	case kReleaseDelAll:
		args.add("-d");
		args.add("-f");
		break;
	case kReleaseDelCvsInfo:
		args.add("-e");
		break;
	}

	args.add(folder);

	args.print(uppath);
	launchCVS(uppath, args.Argc(), args.Argv());
}

/*!
	Execute <b>cvs watchers</b> command
	\param handler Selection for the command
*/
void CvsCmdWatchers(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("watchers");

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs editors</b> command
	\param handler Selection for the command
*/
void CvsCmdEditors(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CvsArgs args;
		args.add("editors");

		const char* dir = mfi->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

/*!
	Execute <b>cvs annotate</b> command
	\param handler Selection for the command
	\param annotateType Annotate command type
*/
void CvsCmdAnnotate(KiSelectionHandler& handler, kAnnotateCmdType annotateType)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool noRecurs;
	bool isDate;
	CPStr rev;
	bool force;

	if( annotateType == kAnnotateGraph )
	{
		if( mf->TotalNumFiles() == 1 )
		{
			const MultiFilesEntry& mfe = *mf->begin();

			if( mfe.NumFiles() == 1 )
			{
			  CStr ignorePath, ignoreName, revision;
			  
			  if( mfe.get(0, ignorePath, ignoreName, revision) ) 
				  rev = revision;
			}
		}
	}
	
	if( !CompatGetAnnotate(mf, annotateType, noRecurs, isDate, rev, force) )
		return;

	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		CStr path, filename, ext, base, currRev;
		CStr file;

		for(int i = 0; ; i++)
		{
			if( !mfi->get(i, path, filename, currRev) )
				break;
			
			// note that filename can contain subfolders
			
			GetExtension(filename, base, ext);

			CvsArgs args;
			CStr tmpf(base);

			if( !rev.empty() )
			{
				tmpf << "_ann_";
				tmpf << rev;
				tmpf << '_';
			}
			else
				tmpf << "_ann_";

			// convert subfolder names to regular name
			FlatenSubFolders(tmpf);

			args.add("annotate");
			
			if( noRecurs )
			{
				args.add("-l");
			}

			if( force )
			{
				args.add("-f");
			}
			
			if( !rev.empty() )
			{
				args.add(isDate ? "-D" : "-r");
				args.add(rev);
			}

			args.add(filename);
			args.print(path);
			file = launchCVS(path, args, (const char *)tmpf, ext);

			if( file.empty() )
				continue;

			LaunchDefaultEditor(file);
		}
	}
}

/*!
	Execute <b>erase</b> command
	\param handler Selection for the command
*/
void CvsCmdMoveToTrash(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;
	
	const char* primaryMsg = NULL;
	const char* secondaryMsg = NULL;
	kCvsAlertButtonType defaultButton = kCvsAlertCancelButton;
	
#if defined (TARGET_OS_MAC)
	primaryMsg = _i18n("Are you sure you want to move the selected files to the trash?");
	secondaryMsg = _i18n("The files will be moved into the trash just as if you deleted them from the Finder.");
	defaultButton = kCvsAlertOKButton;
#elif defined (WIN32)
	primaryMsg = _i18n("Are you sure you want to move the selected files to the recycle bin?");
#else
	primaryMsg = _i18n("Are you sure you want to remove the selected files?");
#endif

	CvsAlert cvsAlert(kCvsAlertQuestionIcon, 
		primaryMsg, secondaryMsg);

	if( cvsAlert.ShowAlert(defaultButton) != kCvsAlertOKButton )
	{
		return;
	}
	
	CompatBeginMoveToTrash();
	
	MultiFiles::const_iterator mfi;
	for(mfi = mf->begin(); mfi != mf->end(); ++mfi)
	{
		int count = mfi->NumFiles();
		for(int i = 0; i < count; i++)
		{
			CStr path, fileName, currRev;
			if( mfi->get(i, path, fileName, currRev) )
			{
				UStr fn(path);
				if( !fn.endsWith(kPathDelimiter) )
					fn << kPathDelimiter;
				
				fn << fileName;
				
				if( !CompatMoveToTrash(fn) )
				{
					UStr errText;
					
					// HINT for translate: "Unable to remove '<FILE>' (error <ERROR>)\n"
					errText << _i18n("Unable to remove '") << fn << _i18n("' (error ") << errno << ")\n";
					
					cvs_err( errText.c_str() );
				}
				else
				{
					UStr errText;
#if defined (TARGET_OS_MAC)
					errText << "'" << fn << _i18n("' has been moved successfully to the trash ...\n");
#elif defined (WIN32)
					errText << "'" << fn << _i18n("' has been moved successfully to the recycle bin ...\n");
#else
					errText << "'" << fn << _i18n("' has been removed successfully ...\n");
#endif
					cvs_out( errText.c_str() );
				}
			}
		}
	}
	
	CompatEndMoveToTrash();
}
