diff --git a/applications/fpsvnsync/fpsvnsync.lpi b/applications/fpsvnsync/fpsvnsync.lpi
new file mode 100644
index 000000000..6692988e3
--- /dev/null
+++ b/applications/fpsvnsync/fpsvnsync.lpi
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/applications/fpsvnsync/fpsvnsync.lpr b/applications/fpsvnsync/fpsvnsync.lpr
new file mode 100644
index 000000000..9c31a6d14
--- /dev/null
+++ b/applications/fpsvnsync/fpsvnsync.lpr
@@ -0,0 +1,345 @@
+{ $Id }
+{ svnsync-like utility written with freepascal
+
+ fpsvnsync synchronizes two svn repositories without the need to set
+ revision properties.
+
+ Copyright (C) 2007 Vincent Snijders (vincents@freepascal.org)
+
+ This source 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 of the License, or (at your option)
+ any later version.
+
+ This code 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.
+
+ A copy of the GNU General Public License is available on the World Wide Web
+ at . You can also obtain it by writing
+ to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ MA 02111-1307, USA.
+}
+program fpsvnsync;
+
+{$mode objfpc}{$H+}
+
+uses
+ {$IFDEF UNIX}{$IFDEF UseCThreads}
+ cthreads,
+ {$ENDIF}{$ENDIF}
+ Classes, SysUtils, CustApp,
+ FileUtil,
+ SvnClasses, SvnCommand;
+
+type
+
+ { TSvnMirrorApp }
+
+ TSvnMirrorApp = class(TCustomApplication)
+ private
+ FSourceWC: string;
+ FDestWC: string;
+ function GetRevision(Directory: string): integer;
+ function GetRepositoryRoot(Directory: string): string;
+ public
+ constructor Create(AOwner: TComponent); override;
+ destructor Destroy; override;
+ procedure Run;
+ end;
+
+{ TSvnMirrorApp }
+
+function TSvnMirrorApp.GetRevision(Directory: string): integer;
+var
+ SvnInfo: TSvnInfo;
+begin
+ SvnInfo := TSvnInfo.Create(Directory);
+ Result := SvnInfo.Entry.Revision;
+ SvnInfo.Free;
+end;
+
+function TSvnMirrorApp.GetRepositoryRoot(Directory: string): string;
+var
+ SvnInfo: TSvnInfo;
+begin
+ SvnInfo := TSvnInfo.Create(Directory);
+ Result := SvnInfo.Entry.Repository.Root;
+ SvnInfo.Free;
+end;
+
+procedure TSvnMirrorApp.Run;
+var
+ SourceHead: integer;
+ Revision: integer;
+ XmlOutput: TMemoryStream;
+ SvnResult: LongInt;
+ SvnLog: TSvnLog;
+ SubPath: string;
+ DestRoot: string;
+
+ procedure GetLog;
+ var
+ Command: string;
+ begin
+ writeln('Getting log message for revision ', Revision);
+ Command := Format('log --xml -v -r%d %s', [Revision,FSourceWC]);
+ SvnLog.LoadFromCommand(command);
+ SvnLog.LogEntry[0].SortPaths;
+ SubPath := SvnLog.LogEntry[0].CommonPath;
+ writeln('Finding common path from log messages: ', SubPath);
+ end;
+
+ procedure UpdateWC(const WorkingDir, SubPath: string; Revision: integer);
+ var
+ Command: string;
+ UpdatePath: string;
+ RevisionStr: string;
+ begin
+ UpdatePath := WorkingDir+SubPath;
+ if Revision > 0 then
+ RevisionStr := IntToStr(Revision)
+ else
+ RevisionStr := 'HEAD';
+ writeln(format('Updating %s to revision %s', [UpdatePath, RevisionStr]));
+ Command := Format('up -r%s %s', [RevisionStr, UpdatePath]);
+ writeln('svn ', Command);
+ SvnResult := ExecuteSvnCommand(Command);
+ end;
+
+ procedure GetDiff;
+ var
+ Command: string;
+ Diff: TStrings;
+ begin
+ writeln('Getting diffs between revision ', Revision-1,' and ', Revision);
+ Command := Format('diff -c%d %s', [Revision, FSourceWC+SubPath]);
+ writeln('svn ', Command);
+ XmlOutput.Clear;
+ SvnResult := ExecuteSvnCommand(Command, XmlOutput);
+ XmlOutput.Position := 0;
+ Diff := TStringList.Create;
+ Diff.LoadFromStream(XmlOutput);
+ writeln('Diff contains ', Diff.Count, ' lines');
+ if pos('Property changes on', Diff.Text)>0 then begin
+ writeln('Properties changed');
+ writeln(Diff.Text);
+ Diff.Free;
+ halt(1);
+ end;
+ Diff.Free;
+ end;
+
+ procedure DeleteFiles;
+ var
+ LogEntry: TLogEntry;
+ LogPath: TLogPath;
+ i: integer;
+ DestFile: string;
+ begin
+ LogEntry := SvnLog.LogEntry[0];
+ for i := 0 to LogEntry.PathCount-1 do begin
+ LogPath := LogEntry.Path[i];
+ if LogPath.Action=caDelete then begin
+ DestFile := FDestWC + LogPath.Path;
+ writeln('Deleting ', DestFile);
+ ExecuteSvnCommand('delete '+DestFile);
+ end;
+ end;
+ end;
+
+ procedure CopyChanges;
+ var
+ LogEntry: TLogEntry;
+ LogPath: TLogPath;
+ i: integer;
+ SourceFile, DestFile, Command: string;
+ begin
+ LogEntry := SvnLog.LogEntry[0];
+ for i := 0 to LogEntry.PathCount-1 do begin
+ LogPath := LogEntry.Path[i];
+ DestFile := FDestWC + LogPath.Path;
+ if LogPath.Action in [caModify, caAdd] then begin
+ SourceFile := FSourceWC + LogPath.Path;
+ if LogPath.CopyFromPath<>'' then begin
+ Command := format('copy -r%d %s%s %s',
+ [LogPath.CopyFromRevision,
+ DestRoot, LogPath.CopyFromPath, DestFile]);
+ writeln('svn '+ Command);
+ ExecuteSvnCommand(Command);
+ end;
+ writeln('Copy ', SourceFile, ' to ', DestFile);
+ if DirectoryExists(SourceFile) then
+ ForceDirectory(DestFile)
+ else
+ CopyFile(SourceFile, DestFile, true);
+ if LogPath.Action=caAdd then begin
+ writeln('svn add '+ DestFile);
+ writeln('Result: ',ExecuteSvnCommand('add '+DestFile));
+ end;
+ end;
+ end;
+ end;
+
+ procedure ApplyPropChanges;
+ var
+ SourcePropInfo: TSvnPropInfo;
+ DestPropInfo: TSvnPropInfo;
+ i: Integer;
+
+ function CreatePropInfo(const BaseDir: string): TSvnPropInfo;
+ var
+ Files: TStrings;
+ begin
+ Result := TSvnPropInfo.Create;
+ Files := SvnLog.LogEntry[0].GetFileList(BaseDir);
+ Result.LoadForFiles(Files);
+ Files.Free;
+ end;
+
+ procedure CopyFileProp(SourceProp, DestProp: TSvnFileProp);
+ var
+ j: integer;
+ Command: string;
+ begin
+ if SourceProp.Properties.Text=DestProp.Properties.Text then exit;
+
+ writeln('Properties changed for ', DestProp.FileName);
+ writeln('Source properties');
+ writeln(SourceProp.Properties.Text);
+ writeln('Destination properties');
+ writeln(DestProp.Properties.Text);
+
+ for j:=0 to DestProp.Properties.Count-1 do begin
+ Command := format('propdel %s %s',
+ [DestProp.Properties.Names[j], DestProp.FileName]);
+ writeln('svn ', Command);
+ writeln('svn result: ', ExecuteSvnCommand(Command));
+ end;
+ for j:=0 to SourceProp.Properties.Count-1 do begin
+ Command := format('propset %s "%s" %s',
+ [SourceProp.Properties.Names[j],
+ SourceProp.Properties.ValueFromIndex[j],
+ DestProp.FileName]);
+ writeln('svn ', Command);
+ writeln('svn result: ', ExecuteSvnCommand(Command));
+ end;
+ end;
+
+ begin
+ SourcePropInfo := CreatePropInfo(FSourceWC);
+ DestPropInfo := CreatePropInfo(FDestWC);
+
+ if SourcePropInfo.FileCount<>DestPropInfo.FileCount then begin
+ writeln('FileName number mismatch: ',
+ SourcePropInfo.FileCount, '<>', DestPropInfo.FileCount);
+ halt(2);
+ end;
+
+ for i := 0 to SourcePropInfo.FileCount-1 do begin
+ if Copy(SourcePropInfo[i].FileName, length(FSourceWC)+1, 4000) <> Copy(DestPropInfo[i].FileName, Length(FDestWC)+1, 4000) then begin
+ writeln('FileName mismatch: ',
+ SourcePropInfo[i].FileName, '<>', DestPropInfo[i].FileName);
+ halt(3);
+ end;
+ CopyFileProp(SourcePropInfo[i], DestPropInfo[i]);
+ end;
+
+ SourcePropInfo.Free;
+ DestPropInfo.Free;
+ end;
+
+ procedure CommitChanges;
+ var
+ Command: string;
+ MessageFile: string;
+ Message: TStrings;
+ LogEntry: TLogEntry;
+ begin
+ writeln('Commit to destination');
+ LogEntry := SvnLog.LogEntry[0];
+ MessageFile := SysUtils.GetTempFileName;
+ Message := TStringList.Create;
+ Message.Add(SvnLog.LogEntry[0].Message);
+ Message.Add(
+ Format('Commited by %s at %s', [LogEntry.Author, LogEntry.DisplayDate]));
+ Message.SaveToFile(MessageFile);
+ writeln(Message.Text);
+ Message.Free;
+ Command := Format('commit -F "%s" %s', [MessageFile, FDestWC+LogEntry.CommonPath]);
+ writeln('svn ', Command);
+ writeln('svn commit result: ', ExecuteSvnCommand(Command));
+ DeleteFile(MessageFile);
+ end;
+
+begin
+ SourceHead := GetRevision('-rHEAD '+FSourceWC);
+ writeln(FSourceWC, ' HEAD at revision ', SourceHead);
+
+ Revision := GetRevision('-rHEAD '+FDestWC);
+ writeln(FDestWC, ' HEAD at revision ', Revision);
+
+ DestRoot := GetRepositoryRoot(FDestWC);
+ writeln('------');
+ XmlOutput := TMemoryStream.Create;
+
+ SvnLog := TSvnLog.Create;
+ while (Revision10670 then break;
+ end;
+
+ XmlOutput.Free;
+ SvnLog.Free;
+end;
+
+constructor TSvnMirrorApp.Create(AOwner: TComponent);
+begin
+ inherited Create(AOwner);
+ if ParamCount=2 then begin
+ FSourceWC := ParamStr(1);
+ FDestWC := ParamStr(2);
+ end
+ else
+ begin
+ FSourceWC := 'd:\lazarus\lazmirror\source';
+ FDestWC := 'd:\lazarus\lazmirror\dest';
+ FSourceWC := 'c:\lazarus\lazmirror\source';
+ FDestWC := 'c:\lazarus\lazmirror\dest';
+ end;
+
+end;
+
+destructor TSvnMirrorApp.Destroy;
+begin
+ inherited Destroy;
+end;
+
+var
+ SvnMirrorApp: TSvnMirrorApp;
+
+begin
+ SvnMirrorApp := TSvnMirrorApp.Create(nil);
+ try
+ SvnMirrorApp.Run;
+ finally
+ SvnMirrorApp.Free;
+ end;
+end.
+