lazarus/components/lazsvnpkg/svnlogform.pas
darius 9d2926b3f7 fixed AV when parsing empty messages in SVN log xml
added asking for confirmation on empty message

r17175
initialized author to '(no author)' (used in anonymous repositories)
fixed commit messages (writing them to a temp file and using --file switch)
fixed some AVs in parsing SVN log XML
fixed saving of checked status in SVNSettingsForm

git-svn-id: trunk@17176 -
2008-11-01 18:10:22 +00:00

446 lines
11 KiB
ObjectPascal

{ Copyright (C) 2008 Darius Blaszijk
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 <http://www.gnu.org/copyleft/gpl.html>. You can also obtain it by writing
to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA.
}
unit SVNLogForm;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
ComCtrls, StdCtrls, ButtonPanel, ExtCtrls, Process, Spin, XMLRead, DOM,
Menus, LCLProc;
type
TActionItem = record
Action: string;
Path: string;
CopyPath: string;
CopyRev: string;
end;
{ TSVNLogItem }
TSVNLogItem = class(TObject)
FAuthor: string;
FCount: integer;
FDate: TDateTime;
FMsg: string;
FRevision: integer;
FAction: array of TActionItem;
function GetAction(Index: Integer): TActionItem;
private
public
constructor Create;
destructor Destroy; override;
procedure AddAction(AActionItem: TActionItem);
property Action[Index: Integer]: TActionItem read GetAction;
property Count: integer read FCount write FCount;
property Revision: integer read FRevision write FRevision;
property Author: string read FAuthor write FAuthor;
property Date: TDateTime read FDate write FDate;
property Msg: string read FMsg write FMsg;
end;
{ TSVNLogFrm }
TSVNLogFrm = class(TForm)
mnuShowDiff: TMenuItem;
SVNActionsPopupMenu: TPopupMenu;
RefreshButton: TButton;
ButtonPanel: TButtonPanel;
Label1: TLabel;
LogListView: TListView;
SVNActionsListView: TListView;
SVNLogMsgMemo: TMemo;
SVNLogLimit: TSpinEdit;
Splitter1: TSplitter;
Splitter2: TSplitter;
procedure mnuShowDiffClick(Sender: TObject);
procedure RefreshButtonClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure LogListViewSelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
private
FRepositoryPath: string;
{ private declarations }
LogList: TFPList;
procedure UpdateLogListView;
public
{ public declarations }
procedure Execute(Data: PtrInt);
property RepositoryPath: string read FRepositoryPath write FrepositoryPath;
end;
procedure ShowSVNLogFrm(ARepoPath: string);
implementation
uses
SVNDiffForm, SVNClasses;
procedure ShowSVNLogFrm(ARepoPath: string);
var
SVNLogFrm: TSVNLogFrm;
begin
SVNLogFrm := TSVNLogFrm.Create(nil);
SVNLogFrm.RepositoryPath:=ARepoPath;
SVNLogFrm.ShowModal;
SVNLogFrm.Free;
end;
{ TSVNLogItem }
function TSVNLogItem.GetAction(Index: Integer): TActionItem;
begin
if (Index < 0) or (Index >= Count) then
raise Exception.CreateFmt(rsIndexOutOfBoundsD, [Index]);
Result := FAction[Index];
end;
constructor TSVNLogItem.Create;
begin
//initialize author to unknown, beacuse in anonymous repositories this really happens
Author := rsNoAuthor;
end;
destructor TSVNLogItem.Destroy;
begin
inherited Destroy;
end;
procedure TSVNLogItem.AddAction(AActionItem: TActionItem);
begin
Inc(FCount);
SetLength(FAction, Count);
FAction[Count - 1] := AActionItem;
end;
function FindSVNLogItemByRevision(List: TFPList; RevNo: integer): TSVNLogItem;
function SearchLinear(List: TFPList; RevNo: integer): TSVNLogItem;
var
i: integer;
begin
Result := nil;
for i := 0 to List.Count - 1 do
if TSVNLogItem(List.Items[i]).Revision = RevNo then
begin
Result := TSVNLogItem(List.Items[i]);
exit;
end;
end;
var
tmpRev: integer;
index: integer;
begin
Result := nil;
tmpRev := TSVNLogItem(List.Items[0]).Revision;
//calculate most probable index
index := tmpRev - RevNo;
if (index < 0) or (index >= List.Count) then
//invalid index, so just do a linear search
Result := SearchLinear(List, RevNo)
else
begin
if TSVNLogItem(List.Items[index]).Revision = RevNo then
//found!
Result := TSVNLogItem(List.Items[index])
else
//revision not found on expected location, search linear
Result := SearchLinear(List, RevNo);
end;
end;
{ TSVNLogFrm }
procedure TSVNLogFrm.FormShow(Sender: TObject);
begin
Caption := Format(rsLazarusSVNLog, [RepositoryPath]);
Application.QueueAsyncCall(@Execute, 0);
end;
procedure TSVNLogFrm.LogListViewSelectItem(Sender: TObject; Item: TListItem;
Selected: Boolean);
var
RevNo: integer;
SVNLogItem: TSVNLogItem;
i: integer;
begin
RevNo := StrToInt(Item.Caption);
SVNLogItem := FindSVNLogItemByRevision(LogList, RevNo);
SVNActionsListView.Clear;
if Assigned(SVNLogItem) then
begin
SVNLogMsgMemo.Lines.Text:=SVNLogItem.Msg;
for i := 0 to SVNLogItem.Count - 1 do
with SVNActionsListView.Items.Add do
begin
Caption := SVNLogItem.Action[i].Action;
SubItems.Add(SVNLogItem.Action[i].Path);
SubItems.Add(SVNLogItem.Action[i].CopyPath);
SubItems.Add(SVNLogItem.Action[i].CopyRev);
end;
end
else
SVNLogMsgMemo.Clear;
end;
procedure TSVNLogFrm.UpdateLogListView;
var
i: integer;
LogItem : TSVNLogItem;
begin
LogListView.Clear;
for i := 0 to LogList.Count - 1 do
with LogListView.Items.Add do
begin
LogItem := TSVNLogItem(LogList.Items[i]);
//revision
Caption := IntToStr(LogItem.Revision);
//author
SubItems.Add(LogItem.Author);
//date
SubItems.Add(DateTimeToStr(LogItem.Date));
//message
SubItems.Add(LogItem.Msg);
end;
end;
procedure TSVNLogFrm.RefreshButtonClick(Sender: TObject);
begin
Execute(0);
end;
procedure TSVNLogFrm.mnuShowDiffClick(Sender: TObject);
var
path: string;
i: integer;
begin
{$note implement opening file in source editor}
if Assigned(SVNActionsListView.Selected) then
begin
debugln('TSVNLogFrm.mnuShowDiffClick Path=' ,SVNActionsListView.Selected.SubItems[0]);
path := SVNActionsListView.Selected.SubItems[0];
Delete(path, 1, 1);
i := pos('/', path);
ShowSVNDiffFrm('-r PREV', RepositoryPath + Copy(path, i, length(path) - i + 1));
end;
end;
procedure TSVNLogFrm.FormCreate(Sender: TObject);
begin
LogList := TFPList.Create;
SetColumn(LogListView, 0, 75, rsRevision);
SetColumn(LogListView, 1, 75, rsAuthor);
SetColumn(LogListView, 2, 150, rsDate);
SetColumn(LogListView, 3, 200, rsMessage);
SetColumn(SVNActionsListView, 0, 50, rsAction);
SetColumn(SVNActionsListView, 1, 200, rsPath);
SetColumn(SVNActionsListView, 2, 150, rsCopyFromPath);
SetColumn(SVNActionsListView, 3, 75, rsRevision);
mnuShowDiff.Caption := rsShowDiff;
end;
procedure TSVNLogFrm.FormDestroy(Sender: TObject);
begin
LogList.Free;
end;
procedure TSVNLogFrm.Execute(Data: PtrInt);
var
ActionItem: TActionItem;
ActionNode: TDOMNode;
AProcess: TProcess;
BytesRead: LongInt;
Doc: TXMLDocument;
i: integer;
j: integer;
LogItem: TSVNLogItem;
M: TMemoryStream;
n: LongInt;
Node: TDOMNode;
NodeName: string;
SubNode: TDOMNode;
t: string;
tmpNode: TDOMNode;
procedure AddItem(Node: TDomNode);
begin
with LogListView.Items.Add do
begin
Caption := Node.NodeName;
end;
end;
begin
debugln('TSVNLogFrm.Execute RepositoryPath=' ,RepositoryPath);
AProcess := TProcess.Create(nil);
AProcess.CommandLine := SVNExecutable + ' log --xml --verbose --limit ' + IntToStr(SVNLogLimit.Value) + ' ' + RepositoryPath + ' --non-interactive';
debugln('TSVNLogFrm.Execute CommandLine ' + AProcess.CommandLine);
AProcess.Options := AProcess.Options + [poUsePipes, poStdErrToOutput];
AProcess.ShowWindow := swoHIDE;
AProcess.Execute;
M := TMemoryStream.Create;
BytesRead := 0;
while AProcess.Running do
begin
// make sure we have room
M.SetSize(BytesRead + READ_BYTES);
// try reading it
n := AProcess.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
if n > 0
then begin
Inc(BytesRead, n);
end
else begin
// no data, wait 100 ms
Sleep(100);
end;
end;
// read last part
repeat
// make sure we have room
M.SetSize(BytesRead + READ_BYTES);
// try reading it
n := AProcess.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
if n > 0
then begin
Inc(BytesRead, n);
end;
until n <= 0;
M.SetSize(BytesRead);
ReadXMLFile(Doc, M);
M.Free;
AProcess.Free;
LogList.Clear;
Node := Doc.DocumentElement.FirstChild;
if Assigned(Node) then
begin
repeat
SubNode := Node;
LogItem := TSVNLogItem.Create;
//revision
LogItem.Revision := StrToInt(SubNode.Attributes.Item[0].NodeValue);
//action
ActionItem.CopyRev := '';
ActionItem.CopyPath := '';
for j := 0 to SubNode.ChildNodes.Length - 1 do
begin
tmpNode := SubNode.ChildNodes.Item[j];
if Assigned(tmpNode) then
begin
NodeName := tmpNode.NodeName;
//Author
if NodeName = 'author' then
LogItem.Author := tmpNode.FirstChild.NodeValue;
//Date
if NodeName = 'date' then
LogItem.Date := ISO8601ToDateTime(tmpNode.FirstChild.NodeValue);
//message
if NodeName = 'msg' then
if Assigned(tmpNode.FirstChild) then
LogItem.Msg:=ReplaceLineEndings(tmpNode.FirstChild.NodeValue, LineEnding);
end;
ActionNode := tmpNode.FirstChild;
if Assigned(ActionNode) and Assigned(ActionNode.Attributes) then
repeat
//attributes
for i := 0 to ActionNode.Attributes.Length-1 do
begin
t := ActionNode.Attributes.Item[i].NodeName;
if t = 'action' then
ActionItem.Action := ActionNode.Attributes.Item[i].NodeValue
else
if t = 'copyfrom-rev' then
ActionItem.CopyRev := ActionNode.Attributes.Item[i].NodeValue
else
if t = 'copyfrom-path' then
ActionItem.CopyPath := ActionNode.Attributes.Item[i].NodeValue;
end;
//paths
ActionItem.Path:=ActionNode.FirstChild.NodeValue;
LogItem.AddAction(ActionItem);
ActionNode := ActionNode.NextSibling;
until not Assigned(ActionNode);
end;
LogList.Add(LogItem);
Node := Node.NextSibling;
until not Assigned(Node);
end;
UpdateLogListView;
end;
initialization
{$I svnlogform.lrs}
end.