/*
** 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"
	
#ifdef qMacCvsPP
#	include "AppGlue.mac.h"
#endif

// - clean up the log message for the server.
// - put the good line feeds
// - TODO mac : ISO conversion
static 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 macintosh
		if(c == '\r')
			c = '\n';
#endif /* macintosh */
		*tmp++ = c;
	}
	*tmp++ = '\0';

	return buf;
}

void
CvsCmdUpdateFolder(const char *dir, bool queryOnly)
{	
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to update :");
	if(dir == 0L)
		return;

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

	if(!queryOnly && !CompatGetUpdate(toStdout, noRecurs, resetSticky,
		date, rev, useMostRecent, rev1, rev2, createMissDir, getCleanCopy))
		return;
		
	CvsArgs args;
	if(queryOnly)
		args.add("-n");
	args.add("update");
	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);
		}
	}
	
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void
CvsCmdCommitFolder(const char *dir)
{
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to commit :");
	if(dir == 0L)
		return;
	
	bool norecurs;
	CStr logmsg;
	bool forceCommit;
	bool forceRecurse;
	CPStr rev;
	bool noModuleProgram;
	if(!CompatGetCommit(logmsg, norecurs, forceCommit, forceRecurse, rev, noModuleProgram, dir))
		return;
		
	CvsArgs args;
	args.add("commit");
	if(norecurs)
		args.add("-l");
	if(noModuleProgram)
		args.add("-n");
	if(forceRecurse)
		args.add("-R");
	if(forceCommit)
		args.add("-f");
	args.add("-m");
	args.add(cleanUpLogMsg(logmsg));
	if(!rev.empty())
	{
		args.add("-r");
		args.add(rev);
	}
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void
CvsCmdDiffFolder(const char *dir)
{
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to diff :");

	if(dir == 0L)
		return;
	
	bool noRecurs;
	bool isDate1;
	bool isDate2;
	bool useExtDiff;
	CPStr rev1;
	CPStr rev2;
	bool noWhiteSpace;
	bool ignoreCase;

	if(!CompatGetDiff(noRecurs, isDate1, isDate2, rev1, rev2, useExtDiff, noWhiteSpace, ignoreCase))
		return;

	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(noWhiteSpace)
		args.add("-w");	//--ignore-all-space
	if(ignoreCase)
		args.add("-i");	//--ignore-case

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

void
CvsCmdAddFolder(const char *dir)
{
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to add :");
	if(dir == 0L)
		return;
	
	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
CvsCmdInit(const char *dir)
{
	if(gCvsPrefs.empty())
		return;
	
	bool forceCvsroot;

	if(!CompatGetInit(forceCvsroot))
		return;

	CvsArgs args;

	if(forceCvsroot)
	{
		CStr root;
		
		// extract from the cvsroot
		const char *ccvsroot = gCvsPrefs;
		ccvsroot = Authen::skiptoken(ccvsroot);
		root = gAuthen.token();
		root << ccvsroot;
		args.add("-d");
		args.add(root);
	}

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

void
CvsCmdImportModule(const char *dir)
{	
	if(gCvsPrefs.empty())
		return;

	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to import :");
	if(dir == 0L)
		return;

	ReportConflict * entries;
	ReportWarning * warnings;

	if(!CompatImportFilter(dir, entries, warnings))
		return;
	
	CPStr modname, vendortag, reltag, path(dir);
	CStr logmsg;
	bool useDefIgnore;

	if(!CompatGetImport(modname, logmsg, vendortag, reltag, path, useDefIgnore))
		return;
	
	CvsArgs args;
	args.add("import");

	// 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() ))
		{
			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
				wrap << " -k 'b'";
			
			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
CvsCmdCheckoutModule(const char *dir)
{
	if(gCvsPrefs.empty())
		return;

#ifndef WIN32    
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to checkout to :");
	if(dir == 0L)
		return;
#endif
	
	bool norecurs;
	CPStr modname;
	CPStr path(dir);
	bool toStdout;
	CPStr date;
	CPStr rev;
	bool useMostRecent;
	bool doexport;
	bool forceCvsroot;
	bool overrideCheckoutDir;
	CPStr checkoutDir;

	if( !CompatGetCheckout(modname, path, norecurs,
		toStdout, date, rev, useMostRecent, doexport, forceCvsroot,
		overrideCheckoutDir, checkoutDir) )
	{
		return;
	}

	CvsArgs args;

	if(forceCvsroot)
	{
		CStr root;
		
		// extract from the cvsroot
		const char *ccvsroot = gCvsPrefs;
		ccvsroot = Authen::skiptoken(ccvsroot);
		root = gAuthen.token();
		root << ccvsroot;
		args.add("-d");
		args.add(root);
	}

	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(!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
CvsCmdRtagCreate(void)
{
	if(gCvsPrefs.empty())
		return;
	
	bool norecurs;
	bool useMostRecent;
	bool overwriteExisting;
	bool lookAttic;
	CPStr modname;
	CPStr tagname;
	CPStr date;
	CPStr rev;

	if(!CompatRtagCreate(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(void)
{
	if(gCvsPrefs.empty())
		return;
	
	bool norecurs;
	bool lookAttic;
	CPStr modname;
	CPStr tagname;

	if(!CompatRtagDelete(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(void)
{
	if(gCvsPrefs.empty())
		return;
	
	bool norecurs;
	bool useMostRecent;
	bool lookAttic;
	CPStr modname;
	CPStr branchname;
	CPStr date;
	CPStr rev;

	if(!CompatRtagBranch(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
CvsCmdLogin(void)
{
	if(gCvsPrefs.empty())
		return;
	
	if(gAuthen.kind() != pserver)
	{
		cvs_err("Set the password authentication first in the preferences !\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("Set the password authentication first in the preferences !\n");
		return;
	}
	
	gCvsPrefs.SetHasLoogedIn(false);

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

void
CvsCmdUpdateFiles(MultiFiles *mfarg, bool queryOnly)
{	
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to update :", mf))
			return;
		mfarg = &mf;
	}
	
	bool toStdout;
	bool noRecurs;
	bool resetSticky;
	CPStr date;
	CPStr rev;
	bool useMostRecent;
	CPStr rev1;
	CPStr rev2;
	bool createMissDir;
	bool getCleanCopy;

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

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

void
CvsCmdCommitFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to commit :", mf))
			return;
		mfarg = &mf;
	}
	
	bool firstTime = true;
	bool norecurs;
	CStr logmsg;
	bool forceCommit;
	bool forceRecurse;
	CPStr rev;
	bool noModuleProgram;
	while(mfarg->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.
			const char *tmplDir = 0L;
			CStr pathTmpl, dontCareFile;
			if(mfarg->get(0, pathTmpl, dontCareFile))
				tmplDir = pathTmpl;

			if(!CompatGetCommit(logmsg, norecurs, forceCommit, forceRecurse, rev, noModuleProgram, tmplDir, mfarg))
				return;

			firstTime = false;
		}

		CvsArgs args;
		args.add("commit");
		if(norecurs)
			args.add("-l");
		if(noModuleProgram)
			args.add("-n");
		if(forceRecurse)
			args.add("-R");
		if(forceCommit)
			args.add("-f");
		args.add("-m");
		args.add(cleanUpLogMsg(logmsg));
		if(!rev.empty())
		{
			args.add("-r");
			args.add(rev);
		}
		const char *dir = mfarg->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

#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
CvsCmdDiffFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to diff :", mf))
			return;
		mfarg = &mf;
	}
	
	bool noRecurs;
	bool isDate1;
	bool isDate2;
	bool useExtDiff;
	CPStr rev1;
	CPStr rev2;
	bool noWhiteSpace;
	bool ignoreCase;
	bool extDiffResult;

	if(!CompatGetDiff(noRecurs, isDate1, isDate2, rev1, rev2, useExtDiff, noWhiteSpace, ignoreCase, true))
		return;

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

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

				CvsArgs args;
				CStr tmpf(base);
				if(!rev1.empty())
				{
					tmpf << '_';
					tmpf << rev1;
					tmpf << '_';
				}
				else
					tmpf << "_orig_";

				// 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);
				}
				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 (!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(noWhiteSpace)
				args.add("-w");	//--ignore-all-space
			if(ignoreCase)
				args.add("-i");	//--ignore-case

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

void
CvsCmdLogFiles(MultiFiles *mfarg, bool outGraph, void *defWnd)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to log :", mf))
			return;
		mfarg = &mf;
	}
	
	bool noRecurs;
	bool defBranch;
	CPStr date;
	bool headerOnly;
	bool noTags;
	bool onlyRCSfile;
	bool hasRev;
	CPStr rev;
	CPStr states;
	bool hasUser;
	CPStr users;

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

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

class AddCvsArgs : public CvsArgs
{
public:
	AddCvsArgs() : gotProblem(false) {}
	
	virtual void addfile(const char *arg, const char *dir = 0L,
		const FSSpec *spec = 0L);
	
	inline bool HasProblem(void) {return gotProblem;}
protected:
	bool gotProblem;
};

void AddCvsArgs::addfile(const char *arg, const char *dir, const FSSpec *spec)
{
	CvsArgs::addfile(arg, dir, spec);
	
	if(!gCvsPrefs.AddControl())
		return;

#ifdef macintosh
	// here a bug of MSL : it refuses to open a file beginning by
	// ".". So we skip this file out of the test and warn the user.
	if(arg[0] == '.')
	{
		cvs_out("Warning : \'%s\' cannot be verified due to a bug in MW MSL, please\n", arg);
		cvs_out("          verify manually \'%s\' *is* a TEXT file\n", arg);
		return;
	}
#endif

	kTextBinTYPE state = FileIsText(arg, dir, spec);
	switch(state)
	{
		case kFileIsOK:
			break;
		case kFileMissing:
			// we can resurect a file which is missing by re-adding it
			break;
		case kFileIsAlias:
			cvs_err("Warning : \'%s\' is an alias\n", arg);
			gotProblem = true;
			break;
		case kFileInvalidName:
			cvs_err("Warning : \'%s\' has an invalid name\n", arg);
			gotProblem = true;
			break;
		case kTextWrongLF:
			cvs_err("Warning : \'%s\' seems to have the wrong line feed for this machine, you should correct it first\n", arg);
			gotProblem = true;
			break;
		case kTextIsBinary:
			cvs_err("Warning : \'%s\' seems to be \'binary\', you should use the \'Add binary files...\' command instead...\n", arg);
			gotProblem = true;
			break;
		case kTextEscapeChar:
#ifdef macintosh
			if(gCvsPrefs.IsoConvert() == ISO8559_none)
			{
				cvs_err("Warning : \'%s\' has some escape characters in it (0x00-0x20, 0x80-0xFF),\n", arg);
				cvs_err("Warning   so you may need to have the ISO8559 translation option set in the preferences\n");
				cvs_err("Warning   before commiting\n");
			}
#else /* !macintosh */
			cvs_err("Warning : \'%s\' has some escape characters in it (0x00-0x20, 0x80-0xFF), you should correct it first\n", arg);
#endif /* !macintosh */
			break;
		case kTextWrongSig:
			cvs_err("Warning : \'%s\' has a file type different from \'TEXT\', you should correct it first\n", arg);
			gotProblem = true;
			break;
		default:
			cvs_err("Warning : \'%s\' has some unknown problems\n", arg);
			gotProblem = true;
			break;
	}
}

void
CvsCmdAddFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to add :", mf))
			return;
		mfarg = &mf;
	}
	
	while(mfarg->next())
	{
		AddCvsArgs args;
		args.add("add");
		const char *dir = mfarg->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());
		}
	}
}

class AddBinCvsArgs : public CvsArgs
{
public:
	AddBinCvsArgs() : gotProblem(false) {}
	
	virtual void addfile(const char *arg, const char *dir = 0L,
		const FSSpec *spec = 0L);
	
	inline bool HasProblem(void) {return gotProblem;}
protected:
	bool gotProblem;
};

void AddBinCvsArgs::addfile(const char *arg, const char *dir, const FSSpec *spec)
{
	CvsArgs::addfile(arg, dir, spec);
	
	if(!gCvsPrefs.AddControl())
		return;
	
#ifdef macintosh
	// here a bug of MSL : it refuses to open a file beginning by
	// ".". So we skip this file out of the test and warn the user.
	if(arg[0] == '.')
	{
		cvs_out("Warning : \'%s\' cannot be verified due to a bug in MW MSL, please\n", arg);
		cvs_out("          verify manually \'%s\' *is not* a TEXT file\n", arg);
		return;
	}
#endif

	kTextBinTYPE state = FileIsBinary(arg, dir, spec);

	switch(state)
	{
		case kFileIsOK:
			break;
		case kFileMissing:
			// we can resurect a file which is missing by re-adding it
			break;
		case kFileIsAlias:
			cvs_err("Warning : \'%s\' is an alias\n", arg);
			gotProblem = true;
			break;
		case kFileInvalidName:
			cvs_err("Warning : \'%s\' has an invalid name\n", arg);
			gotProblem = true;
			break;
		case kBinIsText:
			cvs_err("Warning : \'%s\' seems to be a text file, you should correct it first\n", arg);
			gotProblem = true;
			break;
		case kBinWrongSig:
			cvs_err("Warning : \'%s\' has a file type of \'TEXT\', you should correct it first\n", arg);
			gotProblem = true;
			break;
		default:
			cvs_err("Warning : \'%s\' has some unknown problems\n", arg);
			gotProblem = true;
			break;
	}
}

void
CvsCmdAddBinaryFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select binary files to add :", mf))
			return;
		mfarg = &mf;
	}
	
	while(mfarg->next())
	{
		AddBinCvsArgs args;
		args.add("add");
		args.add("-kb");
		const char *dir = mfarg->add(args);

		if(!args.HasProblem() || !CvsAlert("Some problems have been found while adding binary files and should be corrected",
				"Cancel", "Ignore"))
		{
			args.print(dir);
			launchCVS(dir, args.Argc(), args.Argv());
		}
	}
}

class RemoveCvsArgs : public CvsArgs
{
public:
	virtual void addfile(const char *arg, const char *dir = 0L,
		const FSSpec *spec = 0L);
};

void RemoveCvsArgs::addfile(const char *arg, const char *dir, const FSSpec *spec)
{
	CvsArgs::addfile(arg, dir, spec);

	CStr fullpath(dir);
	if(!fullpath.endsWith(kPathDelimiter))
		fullpath << kPathDelimiter;
	fullpath << arg;

	// check the file is not already deleted
	struct stat sb;
	if (stat(dir, &sb) != -1 && S_ISDIR(sb.st_mode) &&
		stat(fullpath, &sb) == -1 && errno == ENOENT)
	{
		return;
	}

	if(!CompatMoveToTrash(arg, dir, spec))
#ifdef WIN32
		cvs_err("Unable to send \'%s\' to the recycle bin\n", arg);
	else
		cvs_out("\'%s\' has been moved successfully to the recycle bin...\n", arg);
#elif !defined(macintosh)
		cvs_err("Unable to send \'%s\' to the trash\n", arg);
	else
		cvs_out("\'%s\' has been moved successfully to trash...\n", arg);
#else
	{}
#endif
}

void
CvsCmdRemoveFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to remove :", mf))
			return;
		mfarg = &mf;
	}
	
	while(mfarg->next())
	{
		RemoveCvsArgs args;
		
		CompatBeginMoveToTrash();
		
		args.add("remove");
		const char *dir = mfarg->add(args);
		args.print(dir);
		
		CompatEndMoveToTrash();
		
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

void
CvsCmdStatusFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to status :", mf))
			return;
		mfarg = &mf;
	}
	
	while(mfarg->next())
	{
		CvsArgs args;
		args.add("status");
		args.add("-v");
		const char *dir = mfarg->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

void
CvsCmdCancelChangesFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to cancel changes :", mf))
			return;
		mfarg = &mf;
	}
		
	while(mfarg->next())
	{
		RemoveCvsArgs args;
		args.add("update");
		const char *dir = mfarg->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

void CvsCmdLine(void)
{
	CvsArgs args(false);
	const char *dir;
	if(!CompatGetCommand(args, dir))
		return;
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void
CvsCmdLogFolder(const char *dir)
{	
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to log :");
	if(dir == 0L)
		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(noRecurs, defBranch, date,
		headerOnly, noTags, onlyRCSfile, hasRev,
		rev, states, hasUser, users))
			return;
	
	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);
	}
	
	args.print(dir);
	CvsLogParse(dir, args, false, NULL);
}

void
CvsCmdStatusFolder(const char *dir)
{	
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to get status :");
	if(dir == 0L)
		return;
	
	CvsArgs args;
	args.add("status");
	args.add("-v");
	
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void
CvsCmdLockFiles(MultiFiles *mfarg)
{	
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to lock :", mf))
			return;
		mfarg = &mf;
	}
	
	while(mfarg->next())
	{
		CvsArgs args;
		args.add("admin");
		args.add("-l");

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

void
CvsCmdUnlockFiles(MultiFiles *mfarg)
{	
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to unlock :", mf))
			return;
		mfarg = &mf;
	}
	
	while(mfarg->next())
	{
		CvsArgs args;
		args.add("admin");
		args.add("-u");

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

void
CvsCmdTagCreateFiles(MultiFiles *mfarg)
{	
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to tag :", mf))
			return;
		mfarg = &mf;
	}

	bool noRecurs, overwriteExisting, checkUnmod;
	CPStr tagName;
	if(!CompatTagCreate(noRecurs, overwriteExisting, tagName, checkUnmod))
		return;
	
	while(mfarg->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 = mfarg->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

void
CvsCmdTagDeleteFiles(MultiFiles *mfarg)
{	
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to untag :", mf))
			return;
		mfarg = &mf;
	}

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

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

void
CvsCmdTagBranchFiles(MultiFiles *mfarg)
{	
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to branch :", mf))
			return;
		mfarg = &mf;
	}

	bool noRecurs, checkUnmod;
	CPStr branchName;
	if(!CompatTagBranch(noRecurs, branchName, checkUnmod))
		return;
	
	while(mfarg->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 = mfarg->add(args);
		args.print(dir);
		launchCVS(dir, args.Argc(), args.Argv());
	}
}

void
CvsCmdLockFolder(const char *dir)
{	
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to lock :");
	if(dir == 0L)
		return;
	
	CvsArgs args;
	args.add("admin");
	args.add("-l");
	
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void
CvsCmdUnlockFolder(const char *dir)
{	
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to unlock :");
	if(dir == 0L)
		return;
	
	CvsArgs args;
	args.add("admin");
	args.add("-u");
	
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void
CvsCmdTagCreateFolder(const char *dir)
{	
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to tag :");
	if(dir == 0L)
		return;
	
	bool noRecurs, overwriteExisting, checkUnmod;
	CPStr tagName;
	if(!CompatTagCreate(noRecurs, overwriteExisting, tagName, checkUnmod))
		return;
	
	CvsArgs args;
	args.add("tag");
	if(noRecurs)
		args.add("-l");
	if(overwriteExisting)
		args.add("-F");
	if(checkUnmod)
		args.add("-c");
	args.add(tagName);
	
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void
CvsCmdTagDeleteFolder(const char *dir)
{	
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to untag :");
	if(dir == 0L)
		return;
	
	bool noRecurs;
	CPStr tagName;
	if(!CompatTagDelete(noRecurs, tagName))
		return;
	
	CvsArgs args;
	args.add("tag");
	args.add("-d");
	if(noRecurs)
		args.add("-l");
	args.add(tagName);
	
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void
CvsCmdTagBranchFolder(const char *dir)
{	
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to branch :");
	if(dir == 0L)
		return;
	
	bool noRecurs, checkUnmod;
	CPStr tagName;
	if(!CompatTagBranch(noRecurs, tagName, checkUnmod))
		return;
	
	CvsArgs args;
	args.add("tag");
	args.add("-b");
	if(noRecurs)
		args.add("-l");
	if(checkUnmod)
		args.add("-c");
	args.add(tagName);
	
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void CvsCmdEditFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select the files to edit :", mf))
			return;
		mfarg = &mf;
	}
	
	while(mfarg->next())
	{
		CvsArgs args;
		args.add("edit");

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

void CvsCmdUneditFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select the files to unedit :", mf))
			return;
		mfarg = &mf;
	}
	
	while(mfarg->next())
	{
		CvsArgs args;
		args.add("unedit");

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

void CvsCmdWatchOnFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select the files to watch :", mf))
			return;
		mfarg = &mf;
	}
	
	while(mfarg->next())
	{
		CvsArgs args;
		args.add("watch");
		args.add("add");

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

void CvsCmdWatchOffFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select the files to unwatch :", mf))
			return;
		mfarg = &mf;
	}
	
	while(mfarg->next())
	{
		CvsArgs args;
		args.add("watch");
		args.add("remove");

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

void CvsCmdWatchersFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select the files to get the watchers from :", mf))
			return;
		mfarg = &mf;
	}
	
	while(mfarg->next())
	{
		CvsArgs args;
		args.add("watchers");

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

void CvsCmdEditorsFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select the files to get the editors from :", mf))
			return;
		mfarg = &mf;
	}
	
	while(mfarg->next())
	{
		CvsArgs args;
		args.add("editors");

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

void CvsCmdEditFolder(const char *dir)
{
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to edit :");
	if(dir == 0L)
		return;
	
	CvsArgs args;
	args.add("edit");
	
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void CvsCmdUneditFolder(const char *dir)
{
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to unedit :");
	if(dir == 0L)
		return;
	
	CvsArgs args;
	args.add("unedit");
	
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void CvsCmdWatchOnFolder(const char *dir)
{
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to watch :");
	if(dir == 0L)
		return;
	
	CvsArgs args;
	args.add("watch");
	args.add("add");
	
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void CvsCmdWatchOffFolder(const char *dir)
{
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to unwatch :");
	if(dir == 0L)
		return;
	
	CvsArgs args;
	args.add("watch");
	args.add("remove");
	
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void CvsCmdReleaseFolder(const char *dir)
{
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to release :");
	if(dir == 0L)
		return;

	CStr uppath, folder;
	if( !SplitPath(dir, uppath, folder) )
	{
		cvs_err("Unable to convert path name '%s'\n", dir);
		//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(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 CvsCmdWatchersFolder(const char *dir)
{
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to get the watchers from :");
	if(dir == 0L)
		return;

	CvsArgs args;
	args.add("watchers");
	
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

void CvsCmdEditorsFolder(const char *dir)
{
	if(gCvsPrefs.empty())
		return;
	
	if(dir == 0L)
		dir = BrowserGetDirectory("Select a directory to get the editors from :");
	if(dir == 0L)
		return;

	CvsArgs args;
	args.add("editors");
	
	args.print(dir);
	launchCVS(dir, args.Argc(), args.Argv());
}

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

void
CvsCmdAnnotateFiles(MultiFiles *mfarg)
{
	if(gCvsPrefs.empty())
		return;
	
	MultiFiles mf;
	if(mfarg == 0L)
	{
		if(!BrowserGetMultiFiles("Select files to annotate :", mf))
			return;
		mfarg = &mf;
	}

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

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

	while(mfarg->next())
	{
		CStr path, filename, ext, base;
		CStr file;
		for(int i = 0; ; i++)
		{
			if(!mfarg->get(i, path, filename))
				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);
		}
	}
}
