/*
** 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 "wincvs.h"

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

#include "CvsCommands.h"
#include "CvsPrefs.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 "TextBinary.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 "BrowseViewHandlers.h"
	
#ifdef qMacCvsPP
#	include "AppGlue.mac.h"
#endif

// - clean up the log message for the server.
// - put the good line feeds
// - 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;
}

#ifdef WIN32
// Windows client may provide file names pointing into subfolders.
// The function below removes folder separators by replacing them with dots,
// the reason is that temporary file could be created without creating
// matching subfolders in the temporary file folder
static void FlatenSubFolders(CStr& tmpf)
{
	char* cp = (char*)tmpf;
	if (cp)
	{
		while(*cp)
		{
			if (*cp == kPathDelimiter)
			{
				*cp = '.';
			}
			cp++;
		}
	}	
}
#else
#define FlatenSubFolders(tmpf) // no-op on other platforms
#endif

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 hadler is properly setup to carry on launching command
	\param handler Command handler to be tested
	\param mf Returned selection as MultiFiles
	\return true if handler is properly setup, false otherwise
*/
static bool CheckSelectionHandler(KiSelectionHandler& handler, MultiFiles*& mf)
{
	if( gCvsPrefs.empty() )
	{
		return false;
	}

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

	return true;
}

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());
}

void CvsCmdLogin(void)
{
	if( gCvsPrefs.empty() )
		return;
	
	if( gAuthen.kind() != pserver )
	{
		cvs_err("Logging in is only required with pserver authentication (see Preferences dialog).\n"
			"Please consult the CVS manual for more details.\n");
		
		return;
	}
	
	if( gCvsPrefs.HasLoogedIn() && gCvsPrefs.LogoutTimeOut() > 0 )
	{
		cvs_err("Making automatic logout:\n");
		CvsCmdLogout();
		WaitForCvs();
	}
	
	gCvsPrefs.SetHasLoogedIn(true);

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

void CvsCmdLogout(void)
{
	if( gCvsPrefs.empty() )
		return;
	
	if( gAuthen.kind() != pserver )
	{
		cvs_err("Logging out requires pserver authentication (see Preferences dialog).\n"
			"Please consult the CVS manual for more details.\n");
		
		return;
	}
	
	gCvsPrefs.SetHasLoogedIn(false);

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

void CvsCmdRtagCreate(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

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

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

	CvsArgs args;
	args.add("rtag");
	
	if( norecurs )
		args.add("-l");
	
	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());
}

void CvsCmdRtagDelete(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool norecurs;
	bool lookAttic;
	CPStr modname;
	CPStr tagname;

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

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

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

void CvsCmdRtagBranch(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool norecurs;
	bool useMostRecent;
	bool lookAttic;
	CPStr modname;
	CPStr branchname;
	CPStr date;
	CPStr rev;

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

	CvsArgs args;
	args.add("rtag");
	args.add("-b");
	
	if( norecurs )
		args.add("-l");
	
	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());
}

void CvsCmdCheckoutModule(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	const char* dir = mf->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;

	if( !CompatGetCheckout(mf, modname, path, norecurs,
		toStdout, date, rev, useMostRecent, rev1, rev2, doexport, forceCvsroot,
		overrideCheckoutDir, checkoutDir) )
	{
		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( 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( !date.empty() )
	{
		args.add("-D");
		args.add(date);
	}
	
	if( !rev.empty() )
	{
		args.add("-r");
		args.add(rev);
	}

	if( doexport && rev.empty() && date.empty() )
	{
		args.add("-r");
		args.add("HEAD");
	}

	if( overrideCheckoutDir )
	{
		args.add("-d");
		args.add(checkoutDir);
	}

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

void CvsCmdImportModule(KiSelectionHandler& handler)
{	
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	while( mf->next() )
	{
		ReportConflict* entries;
		ReportWarning* warnings;
		const char* dir = mf->getdir();
		
		if( !CompatImportFilter(dir, entries, warnings) )
			return;
		
		CPStr modname, vendortag, reltag, path(dir);
		CStr logmsg;
		bool useDefIgnore;
		bool useFilesTime;
		
		if( !CompatGetImport(mf, modname, logmsg, vendortag, reltag, path, useDefIgnore, useFilesTime) )
			return;
		
		CvsArgs args;
		args.add("import");
		
		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);
		
		args.add("-m");
		args.add(cleanUpLogMsg(logmsg));
		args.add(modname);
		args.add(vendortag);
		args.add(reltag);
		
		args.print(path);
		launchCVS(path, args.Argc(), args.Argv());
	}
}

void CvsCmdCommandLine(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	CvsArgs args(false);
	const char *dir;

	if( !CompatGetCommand(mf, args, dir) )
		return;

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

void CvsCmdCancelChanges(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	while( mf->next() )
	{
		RemoveCvsArgs args;
		args.add("update");

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

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;

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

	while( mf->next() )
	{
		CvsArgs args;

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

		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 = mf->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

void CvsCmdCommit(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool firstTime = true;
	bool norecurs;
	CStr logmsg;
	bool forceCommit;
	bool forceRecurse;
	CPStr rev;
	bool noModuleProgram;
	bool checkValidEdits;	

	while( mf->next() )
	{
		if( firstTime )
		{
			// we have to give the path where the commit is done is
			// the user expects to fill the CVS/Template file. we just
			// grab the first one for this purpose.
			CStr pathTmpl;
			mf->getdir(0, pathTmpl);

			if( !CompatGetCommit(mf, logmsg, norecurs, forceCommit, forceRecurse, rev, noModuleProgram, checkValidEdits, pathTmpl) )
				return;

			firstTime = false;
		}

		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 = mf->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

void CvsCmdDiff(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool noRecurs;
	bool isDate1;
	bool isDate2;
	bool useExtDiff;
	bool useUnifiedDiff;
	CPStr rev1;
	CPStr rev2;
	bool noWhiteSpace;
	bool noBlankLines;
	bool ignoreCase;
	bool extDiffResult;

	if( !CompatGetDiff(mf, noRecurs, isDate1, isDate2, rev1, rev2, useExtDiff, useUnifiedDiff, noWhiteSpace, noBlankLines, ignoreCase, mf->NumFiles() > 0) )
		return;

	while( mf->next() )
	{
		extDiffResult = false;

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

			for(int i = 0; ; i++)
			{
				if( !mf->get(i, path, filename, currRev) )
					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 
				{
 					args.add("-r");
					args.add(currRev);
  				}

				args.add(filename);
				args.print(path);
				file1 = launchCVS(path, args, (const char *)tmpf, ext);
#ifdef qMacCvsPP
				decodeCVS (file1);
#endif

				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);
#ifdef qMacCvsPP
					decodeCVS (file2);
#endif
				}

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

				extDiffResult = LaunchDiff(file1, file2);
			}
		}

		if( !mf->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(useUnifiedDiff)
				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

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

void CvsCmdAdd(KiSelectionHandler& handler, kAddCmdType addType /*= kAddNormal*/)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	while( mf->next() )
	{
		if( mf->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 = mf->add(*args);
			
			if( !args->HasProblem() ||
				!CvsAlert("Some problems have been found while adding files and should be corrected", "Cancel", "Ignore") )
			{
				args->print(dir);
				launchCVS(dir, args->Argc(), args->Argv());
			}

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

			if( !SplitPath(dir, uppath, folder) )
			{
				cvs_err("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());
		}
	}
}

void CvsCmdRemove(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	while( mf->next() )
	{
		RemoveCvsArgs args;
		
		CompatBeginMoveToTrash();
		
		args.add("remove");
		const char *dir = mf->add(args);
		args.print(dir);
		
		CompatEndMoveToTrash();
		
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

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 noTags;
	bool onlyRCSfile;
	bool hasRev;
	CPStr rev;
	CPStr states;
	bool hasUser;
	CPStr users;

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

	while( mf->next() )
	{
		CvsArgs args;
		args.add("log");

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

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

		if( headerOnly )
			args.add("-h");

		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 = mf->add(args);
		args.print(dir);
		CvsLogParse(dir, args, outGraph, defWnd);
	}
}

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;

	while( mf->next() )
	{
		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 = mf->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

void CvsCmdLock(KiSelectionHandler& handler)
{	
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	while( mf->next() )
	{
		CvsArgs args;
		args.add("admin");
		args.add("-l");

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

void CvsCmdUnlock(KiSelectionHandler& handler)
{	
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	while( mf->next() )
	{
		CvsArgs args;
		args.add("admin");
		args.add("-u");

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

void CvsCmdTagCreate(KiSelectionHandler& handler)
{	
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool noRecurs, overwriteExisting, checkUnmod;
	CPStr tagName;

	if( !CompatTagCreate(mf, noRecurs, overwriteExisting, tagName, checkUnmod) )
		return;
	
	while( mf->next() )
	{
		CvsArgs args;
		args.add("tag");

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

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

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

		args.add(tagName);

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

void CvsCmdTagDelete(KiSelectionHandler& handler)
{	
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool noRecurs;
	CPStr tagName;

	if( !CompatTagDelete(mf, noRecurs, tagName) )
		return;
	
	while( mf->next() )
	{
		CvsArgs args;
		args.add("tag");
		args.add("-d");
		
		if( noRecurs )
			args.add("-l");

		args.add(tagName);

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

void CvsCmdTagBranch(KiSelectionHandler& handler)
{	
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	bool noRecurs, checkUnmod;
	CPStr branchName;

	if( !CompatTagBranch(mf, noRecurs, branchName, checkUnmod) )
		return;
	
	while( mf->next() )
	{
		CvsArgs args;
		args.add("tag");
		args.add("-b");

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

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

		args.add(branchName);

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

void CvsCmdEdit(KiSelectionHandler& handler, kEditCmdType editType /*= kNormal*/)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	while( mf->next() )
	{
		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 = mf->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

void CvsCmdUnedit(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	while( mf->next() )
	{
		CvsArgs args;
		args.add("unedit");

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

void CvsCmdWatchOn(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	while( mf->next() )
	{
		CvsArgs args;
		args.add("watch");
		args.add("add");

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

void CvsCmdWatchOff(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	while( mf->next() )
	{
		CvsArgs args;
		args.add("watch");
		args.add("remove");

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

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("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 = ".";
	}

	bool removeDirectory = false;

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

	CvsArgs args;
	args.add("release");
	
	if( removeDirectory )
		args.add("-d");
	
	args.add(folder);
	
	args.print(uppath);
	launchCVS(uppath, args.Argc(), args.Argv());
}

void CvsCmdWatchers(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	while( mf->next() )
	{
		CvsArgs args;
		args.add("watchers");

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

void CvsCmdEditors(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	while( mf->next() )
	{
		CvsArgs args;
		args.add("editors");

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

void CvsCmdAnnotate(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

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

	if( !CompatGetAnnotate(mf, noRecurs, isDate, rev, force) )
		return;

	while( mf->next() )
	{
		CStr path, filename, ext, base, currRev;
		CStr file;

		for(int i = 0; ; i++)
		{
			if( !mf->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);
#ifdef qMacCvsPP
			decodeCVS (file);
#endif

			if( file.empty() )
				continue;

			LaunchViewer(file, true);
		}
	}
}

void CvsCmdMoveToTrash(KiSelectionHandler& handler)
{
	MultiFiles* mf = NULL;
	if( !CheckSelectionHandler(handler, mf) )
		return;

	if( !CvsAlert("Are you sure you want to erase selected files?", "Yes", "No") )
	{
		return;
	}
	
	CompatBeginMoveToTrash();

	while( mf->next() ) 
	{
		int count = mf->NumFiles();
		for (int i = 0; i < count; i++)
		{
			CStr path, fileName, currRev;
			if( mf->get(i, path, fileName, currRev) )
			{
				UStr fn(path);
				UStr errText;
				if( !fn.endsWith(kPathDelimiter) )
					fn << kPathDelimiter;
				fn << fileName;

				if( !CompatMoveToTrash(fn) )
				{
					errText << "Unable to remove '" << fn << "' (error " << errno << ")\n";

					cvs_err( errText.c_str() );
				}
				else
				{
#if TARGET_OS_MAC
					errText << "'" << fn << "' has been moved successfully to the trash...\n";
#else
					errText << "'" << fn << "' has been moved successfully to the recycle bin ...\n";
#endif
					cvs_out( errText.c_str() );
				}
			}
		}
	}
	
	CompatEndMoveToTrash();
#if 0
	while( mf->next() )
	{
		CvsArgs args;
		args.add("editors");

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