% Copyright (C) 2002-2005 David Roundy
%
% 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 2, 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; see the file COPYING. If not, write to
% the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
% Boston, MA 02110-1301, USA.
\darcsCommand{replace}
\begin{code}

{-# OPTIONS_GHC -cpp #-}{-# LANGUAGE CPP #-}moduleDarcs.Commands.Replace(replace)whereimportData.Maybe(isJust)importControl.Monad(unless,filterM)importControl.Applicative((<$>))importDarcs.Commands(DarcsCommand(DarcsCommand,commandName,commandHelp,commandDescription,commandExtraArgs,commandExtraArgHelp,commandCommand,commandPrereq,commandGetArgPossibilities,commandArgdefaults,commandAdvancedOptions,commandBasicOptions),nodefaults)importDarcs.Arguments(DarcsFlag(ForceReplace,Toks),listRegisteredFiles,ignoretimes,umaskOption,tokens,forceReplace,workingRepoDir,fixSubPaths)importDarcs.Repository(withRepoLock,($-),add_to_pending,amInRepository,applyToWorking,readUnrecorded,readRecordedAndPending)importDarcs.Patch(Prim,tokreplace,applyToTree)importDarcs.Patch.Apply(forceTokReplace)importDarcs.Patch.FileName(fn2fp)importDarcs.Patch.Patchy(Apply)importDarcs.Witnesses.Ordered(FL(..),unsafeFL,(+>+),concatFL)importDarcs.Patch.RegChars(regChars)importData.Char(isSpace)importDarcs.RepoPath(SubPath,toFilePath,sp2fn)importDarcs.Repository.Prefs(FileType(TextFile))importDarcs.Diff(treeDiff)importStorage.Hashed.Tree(readBlob,modifyTree,findFile,TreeItem(..),Tree,makeBlobBS)importStorage.Hashed.AnchoredPath(AnchoredPath,floatPath)importqualifiedData.ByteString.LazyasBLimportqualifiedData.ByteStringasBS#include "impossible.h"replaceDescription::StringreplaceDescription="Substitute one word for another."replaceHelp::StringreplaceHelp="In addition to line-based patches, Darcs supports a limited form of\n"++"lexical substitution. Files are treated as sequences of words, and\n"++"each occurrence of the old word is replaced by the new word.\n"++"This is intended to provide a clean way to rename a function or\n"++"variable. Such renamings typically affect lines all through the\n"++"source code, so a traditional line-based patch would be very likely to\n"++"conflict with other branches, requiring manual merging.\n"++"\n"++"Files are tokenized according to one simple rule: words are strings of\n"++"valid token characters, and everything between them (punctuation and\n"++-- FIXME: this heuristic is ham-fisted and silly. Can we drop it?"whitespace) is discarded. By default, valid token characters are\n"++"letters, numbers and the underscore (i.e. [A-Za-z0-9_]). However if\n"++"the old and/or new token contains either a hyphen or period, BOTH\n"++"hyphen and period are treated as valid (i.e. [A-Za-z0-9_.-]).\n"++"\n"++"The set of valid characters can be customized using the --token-chars\n"++"option. The argument must be surrounded by square brackets. If a\n"++"hyphen occurs between two characters in the set, it is treated as a\n"++"set range. For example, in most locales [A-Z] denotes all uppercase\n"++"letters. If the first character is a caret, valid tokens are taken to\n"++"be the complement of the remaining characters. For example, [^:\\n]\n"++"could be used to match fields in the passwd(5), where records and\n"++"fields are separated by newlines and colons respectively.\n"++"\n"++"If you choose to use --token-chars, you are STRONGLY encouraged to do\n"++"so consistently. The consequences of using multiple replace patches\n"++"with different --token-chars arguments on the same file are not well\n"++"tested nor well understood.\n"++"\n"++"By default Darcs will refuse to perform a replacement if the new token\n"++"is already in use, because the replacements would be not be\n"++"distinguishable from the existing tokens. This behaviour can be\n"++"overridden by supplying the --force option, but an attempt to `darcs\n"++"rollback' the resulting patch will affect these existing tokens.\n"++"\n"++"Limitations:\n"++"\n"++"The tokenizer treats files as byte strings, so it is not possible for\n"++"--token-chars to include multi-byte characters, such as the non-ASCII\n"++"parts of UTF-8. Similarly, trying to replace a `high-bit' character\n"++"from a unibyte encoding will also result in replacement of the same\n"++"byte in files with different encodings. For example, an acute a from\n"++"ISO 8859-1 will also match an alpha from ISO 8859-7.\n"++"\n"++"Due to limitations in the patch file format, --token-chars arguments\n"++"cannot contain literal whitespace. For example, [^ \\n\\t] cannot be\n"++"used to declare all characters except the space, tab and newline as\n"++"valid within a word, because it contains a literal space.\n"++"\n"++"Unlike POSIX regex(7) bracket expressions, character classes (such as\n"++"[[:alnum:]]) are NOT supported by --token-chars, and will be silently\n"++"treated as a simple set of characters.\n"-- FIXME: can we just delete the remaining text? It seems more an-- instance of "look how clever I am; I made commutation work" rather-- than information that is actually useful to users.

\end{code}
There is a potentially confusing difference, however, when a replace is
used to make another replace possible:
\begin{verbatim}
$ darcs replace newtoken aaack ./foo.c
$ darcs replace oldtoken newtoken ./foo.c
$ darcs record
\end{verbatim}
will be valid, even if \verb!newtoken! and \verb!oldtoken! are both present
in the recorded version of foo.c, while the sequence
\begin{verbatim}
$ [manually edit foo.c replacing newtoken with aaack]
$ darcs replace oldtoken newtoken ./foo.c
\end{verbatim}
will fail because ``newtoken'' still exists in the recorded version of
\verb!foo.c!. The reason for the difference is that when recording, a
``replace'' patch always is recorded \emph{before} any manual changes,
which is usually what you want, since often you will introduce new
occurrences of the ``newtoken'' in your manual changes. In contrast,
multiple ``replace'' changes are recorded in the order in which
they were made.
\begin{code}

replace::DarcsCommandreplace=DarcsCommand{commandName="replace",commandHelp=replaceHelp,commandDescription=replaceDescription,commandExtraArgs=-1,commandExtraArgHelp=["<OLD>","<NEW>","<FILE> ..."],commandCommand=replaceCmd,commandPrereq=amInRepository,commandGetArgPossibilities=listRegisteredFiles,commandArgdefaults=nodefaults,commandAdvancedOptions=[ignoretimes,umaskOption],commandBasicOptions=[tokens,forceReplace,workingRepoDir]}replaceCmd::[DarcsFlag]->[String]->IO()replaceCmdopts(old:new:relfs)=withRepoLockopts$-\repository->dofs<-fixSubPathsoptsrelfstoks<-chooseToksoptsoldnewletcheckTokentok=unless(isToktokstok)$fail$"'"++tok++"' is not a valid token!"checkTokenoldcheckTokennewwork<-readUnrecordedrepositorycur<-readRecordedAndPendingrepositoryfiles<-filterM(existswork)fspswork<-concatFL.unsafeFL<$>mapM(repltokscurwork)filesadd_to_pendingrepositorypsworkapplyToWorkingrepositoryoptspswork`catch`\e->fail$"Can't do replace on working!\n"++"Perhaps one of the files already contains '"++new++"'?\n"++showewhereftf_=TextFileskipmsgf="Skipping file '"++toFilePathf++"' which isn't in the repository."existstreefile=ifisJust$findFiletree(floatSubPathfile)thenreturnTrueelsedoputStrLn$skipmsgfilereturnFalserepl::String->TreeIO->TreeIO->SubPath->IO(FLPrim)repltokscurworkf=dowork_replaced<-maybeApplyToTreereplace_patchworkcur_replaced<-maybeApplyToTreereplace_patchcurifForceReplace`elem`opts||isJustwork_replaced||isJustcur_replacedthenget_force_replaceftoksworkelsedoputStrLn$"Skipping file '"++f_fp++"'"putStrLn$"Perhaps the recorded version of this "++"file already contains '"++new++"'?"putStrLn$"Use the --force option to override."returnNilFLwheref_fp=toFilePathfreplace_patch=tokreplacef_fptoksoldnewget_force_replace::SubPath->String->TreeIO->IO(FLPrim)get_force_replaceftokstree=doletpath=floatSubPathfcontent<-readBlob$fromJust$findFiletreepathletnewcontent=forceTokReplacetoksnewold(BS.concat$BL.toChunkscontent)tree'=modifyTreetreepath(File.makeBlobBS<$>newcontent)casenewcontentofNothing->bug"weird forcing bug in replace."Just_->dopfix<-treeDiffftftreetree'return$pfix+>+(tokreplacef_fptoksoldnew:>:NilFL)wheref_fp=toFilePathfreplaceCmd__=fail"Usage: darcs replace OLD NEW [FILES]"floatSubPath::SubPath->AnchoredPathfloatSubPath=floatPath.fn2fp.sp2fnmaybeApplyToTree::Applyp=>p->TreeIO->IO(Maybe(TreeIO))maybeApplyToTreepatchtree=catch(Just`fmap`applyToTreepatchtree)(\_->returnNothing)defaultToks::StringdefaultToks="A-Za-z_0-9"filenameToks::StringfilenameToks="A-Za-z_0-9\\-\\."-- | Given a set of characters and a string, returns true iff the-- string contains only characters from the set. A set beginning with-- a caret (@^@) is treated as a complementary set.isTok::String->String->BoolisTok_""=FalseisToktokss=and$map(regCharstoks)s-- | This function checks for @--token-chars@ on the command-line. If-- found, it validates the argument and returns it, without the-- surrounding square brackets. Otherwise, it returns either-- 'defaultToks' or 'filenameToks' as explained in 'replaceHelp'.-- -- Note: Limitations in the current replace patch file format prevents-- tokens and token-char specifiers from containing any whitespace.chooseToks::[DarcsFlag]->String->String->IOStringchooseToks(Tokst:_)ab|lengtht<=2=bad_token_spec$"It must contain more than 2 characters, because "++"it should be enclosed in square brackets"|headt/='['||lastt/=']'=bad_token_spec"It should be enclosed in square brackets"|'^'==headtok&&lengthtok==1=bad_token_spec"Must be at least one character in the complementary set"|anyisSpacet=bad_token_spec"Space is not allowed in the spec"|anyisSpacea=bad_token_spec$spacey_tokena|anyisSpaceb=bad_token_spec$spacey_tokenb|not(isToktoka)=bad_token_spec$not_a_tokena|not(isToktokb)=bad_token_spec$not_a_tokenb|otherwise=returntokwheretok=init$tailt::Stringbad_token_specmsg=fail$"Bad token spec: '"++t++"' ("++msg++")"spacey_tokenx=x++" must not contain any space"not_a_tokenx=x++" is not a token, according to your spec"chooseToks(_:fs)ab=chooseToksfsabchooseToks[]ab=ifisTokdefaultToksa&&isTokdefaultToksbthenreturndefaultTokselsereturnfilenameToks