Windows.SVNAdmin.Mod 26 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031
  1. MODULE SVNAdmin; (** AUTHOR "rstoll"; *)
  2. (*
  3. .svn directories are per default write protected. Changing file properties is only supported in the Win32 version
  4. by kernel calls.
  5. *)
  6. IMPORT
  7. SVNOutput, SVNUtil,
  8. Strings, Dates,
  9. Commands, Files, Kernel32, KernelLog;
  10. CONST
  11. EntryFileFormat* = 8; (* expected svn version *)
  12. TYPE
  13. EntryEntity* = RECORD
  14. Format* : LONGINT; (* 1 *)
  15. Name* : ARRAY 256 OF CHAR; (* 2 *)
  16. NodeKind* : ARRAY 32 OF CHAR; (* 3 *)
  17. Revision* : LONGINT; (* 4 *)
  18. GlobalRemoval* : BOOLEAN;
  19. Url*, UrlConst* : ARRAY 256 OF CHAR; (* 5 *)
  20. RepositoryRoot* : ARRAY 256 OF CHAR; (* 6 *)
  21. Schedule* : ARRAY 32 OF CHAR; (* 7 *)
  22. TextLastUpdated* : ARRAY 32 OF CHAR; (* 8 *)
  23. Checksum* : ARRAY 33 OF CHAR; (* 9 *)
  24. LastChangedDate* : ARRAY 32 OF CHAR; (* 10: len ?= 28? *)
  25. LastChangedRevision* : LONGINT; (* 11 *)
  26. LastChangedAuthor* : ARRAY 256 OF CHAR; (* 12 *)
  27. Props* : ARRAY 256 OF CHAR; (* 13: maybe longer? *)
  28. (*PropsMods : ARRAY 256 OF CHAR; (* 14: maybe longer *)*)
  29. RepositoryUUID* : ARRAY 37 OF CHAR; (* 27 *)
  30. VersionUrl* : ARRAY 256 OF CHAR;
  31. END;
  32. TYPE
  33. TraverseHandler* = PROCEDURE {DELEGATE} ( CONST path : ARRAY OF CHAR; fileEntry : EntryEntity; data : ANY ) : BOOLEAN;
  34. TYPE
  35. Entry* = OBJECT
  36. VAR
  37. adminDir : EntryEntity;
  38. path : ARRAY 256 OF CHAR;
  39. name : ARRAY 32 OF CHAR;
  40. context : Commands.Context;
  41. fAdmin, fAdminTemp : Files.File;
  42. entriesfile, entriesfiletemp, svnpath : ARRAY 256 OF CHAR;
  43. pathIsFile, readGlobalData, readFromTempfile : BOOLEAN;
  44. r : Files.Reader;
  45. w : Files.Writer;
  46. pathLength : LONGINT;
  47. PROCEDURE &Init* ( c: Commands.Context );
  48. BEGIN
  49. context := c;
  50. readGlobalData := FALSE;
  51. END Init;
  52. PROCEDURE ReadVersionUrl* ( CONST filename : ARRAY OF CHAR );
  53. VAR
  54. propsfile, p, n, tmp : ARRAY 256 OF CHAR;
  55. nextFileEntry : BOOLEAN;
  56. pf : Files.File;
  57. pr : Files.Reader;
  58. BEGIN
  59. IF fAdmin # NIL THEN
  60. IF pathIsFile THEN
  61. Files.SplitPath ( path, p, n );
  62. Files.JoinPath ( p, ".svn/all-wcprops", propsfile );
  63. ELSE
  64. Files.JoinPath ( path, ".svn/all-wcprops", propsfile );
  65. END;
  66. IF filename # "" THEN
  67. COPY ( filename, n );
  68. END;
  69. pf := Files.Old ( propsfile );
  70. ASSERT ( pf # NIL );
  71. Files.OpenReader ( pr, pf, 0 );
  72. ASSERT ( pr # NIL );
  73. IF pathIsFile OR (n # "") THEN (* search for the file *)
  74. nextFileEntry := FALSE;
  75. LOOP
  76. pr.Ln ( tmp );
  77. IF pr.res # Files.Ok THEN
  78. KernelLog.String ( "ERROR: didn't find entry in the .svn/all-wcprops file for: " );
  79. KernelLog.String ( p ); KernelLog.String ( " file: " ); KernelLog.String ( n );
  80. KernelLog.Ln;
  81. RETURN;
  82. END;
  83. IF nextFileEntry & (tmp = n) THEN EXIT END;
  84. nextFileEntry := (tmp = "END");
  85. END;
  86. END;
  87. (* get url *)
  88. pr.SkipLn; pr.SkipLn; pr.SkipLn;
  89. pr.Ln ( adminDir.VersionUrl );
  90. END;
  91. END ReadVersionUrl;
  92. PROCEDURE SetPath* ( CONST p : ARRAY OF CHAR; VAR res : WORD );
  93. BEGIN
  94. r := NIL;
  95. readGlobalData := FALSE;
  96. COPY ( p, path );
  97. AnalyzePath ( res );
  98. ASSERT ( (res = SVNOutput.ResOK) OR (res = SVNOutput.ResNOTVERSIONED) );
  99. END SetPath;
  100. PROCEDURE AnalyzePath ( VAR res : WORD );
  101. BEGIN
  102. pathIsFile := FALSE;
  103. res := SVNOutput.ResOK;
  104. IF (path = ".") OR (path = "") THEN
  105. fAdmin := Files.Old (".svn/entries");
  106. IF fAdmin = NIL THEN
  107. res := SVNOutput.ResNOTVERSIONED;
  108. RETURN;
  109. END;
  110. ELSE
  111. Files.JoinPath ( path, ".svn/entries", entriesfile );
  112. fAdmin := Files.Old ( entriesfile );
  113. IF fAdmin = NIL THEN (* our parameter was a file destination *)
  114. Files.SplitPath ( path, svnpath, name );
  115. Files.JoinPath ( svnpath, ".svn/entries", entriesfile );
  116. fAdmin := Files.Old ( entriesfile );
  117. IF fAdmin = NIL THEN
  118. res := SVNOutput.ResNOTVERSIONED;
  119. RETURN;
  120. END;
  121. pathIsFile := TRUE; (* search the file in the svn-entries file *)
  122. Files.JoinPath ( svnpath, ".svn/tmp/od-entries", entriesfiletemp );
  123. ELSE
  124. (* Kernel32.SetFileAttributes ( entriesfile, {} );*)
  125. Files.JoinPath ( path, ".svn/tmp/od-entries", entriesfiletemp );
  126. END;
  127. END;
  128. ASSERT ( fAdmin # NIL );
  129. END AnalyzePath;
  130. PROCEDURE CreateTempfile*;
  131. VAR
  132. res : WORD;
  133. BEGIN
  134. IF SVNUtil.FileExists ( entriesfiletemp ) THEN
  135. Files.Delete ( entriesfiletemp, res );
  136. ASSERT ( res = Files.Ok );
  137. END;
  138. fAdminTemp := Files.New ( entriesfiletemp );
  139. ReadFromTempfile ( TRUE );
  140. END CreateTempfile;
  141. PROCEDURE ReadFromTempfile* ( b : BOOLEAN );
  142. BEGIN
  143. IF b THEN
  144. ASSERT ( fAdmin # NIL );
  145. ASSERT ( fAdminTemp # NIL );
  146. Files.OpenWriter ( w, fAdminTemp, 0 );
  147. Files.OpenReader ( r, fAdmin, 0 );
  148. END;
  149. readFromTempfile := b;
  150. END ReadFromTempfile;
  151. PROCEDURE GetUrl* ( VAR url : ARRAY OF CHAR );
  152. BEGIN
  153. COPY ( adminDir.Url, url );
  154. END GetUrl;
  155. PROCEDURE GetRepo* ( VAR repos : ARRAY OF CHAR );
  156. BEGIN
  157. COPY ( adminDir.RepositoryRoot, repos );
  158. END GetRepo;
  159. PROCEDURE GetVersion* () : LONGINT;
  160. BEGIN
  161. IF ~pathIsFile THEN
  162. RETURN adminDir.Revision;
  163. ELSE
  164. RETURN adminDir.LastChangedRevision;
  165. END;
  166. END GetVersion;
  167. PROCEDURE SkipGlobalData;
  168. VAR
  169. temp : ARRAY 256 OF CHAR;
  170. start: ARRAY 2 OF CHAR;
  171. BEGIN
  172. (* IF ~readGlobalData THEN
  173. readGlobalData := TRUE;*)
  174. ASSERT ( fAdmin # NIL );
  175. NEW( r, fAdmin, 0 );
  176. start[0] := CHR(12); start[1] := 0X;
  177. REPEAT
  178. r.Ln ( temp );
  179. IF readFromTempfile THEN
  180. WriteString ( w, temp );
  181. END;
  182. UNTIL Strings.StartsWith2(start, temp);
  183. (* END;*)
  184. END SkipGlobalData;
  185. (* read all lines until we reach end of entry *)
  186. PROCEDURE SkipReaderToEOE;
  187. VAR
  188. temp : ARRAY 256 OF CHAR;
  189. start: ARRAY 2 OF CHAR;
  190. BEGIN
  191. ASSERT ( r # NIL );
  192. start[0] := CHR(12); start[1] := 0X;
  193. REPEAT
  194. r.Ln ( temp );
  195. IF r.res # Files.Ok THEN RETURN END;
  196. UNTIL Strings.StartsWith2(start, temp);
  197. END SkipReaderToEOE;
  198. PROCEDURE IsItemVersioned* ( CONST filename : ARRAY OF CHAR ) : BOOLEAN;
  199. VAR
  200. temp : ARRAY 256 OF CHAR;
  201. BEGIN
  202. (* global data hasn't been read, so we need to do it now *)
  203. SkipGlobalData;
  204. readGlobalData := FALSE;
  205. ASSERT ( r # NIL );
  206. LOOP
  207. r.Ln(temp); (* 2: read name of file *)
  208. IF r.res # Files.Ok THEN RETURN FALSE END;
  209. IF readFromTempfile THEN
  210. WriteString ( w, temp );
  211. END;
  212. Strings.TrimWS(temp);
  213. IF filename = temp THEN
  214. RETURN TRUE;
  215. ELSE
  216. IF readFromTempfile THEN
  217. ReadWriteToEOE;
  218. ELSE
  219. SkipReaderToEOE;
  220. END;
  221. IF r.res # Files.Ok THEN RETURN FALSE END;
  222. END;
  223. END;
  224. END IsItemVersioned;
  225. (* add everything in the directory recursively
  226. - path
  227. - name: name of the directory inside the path which will be added
  228. - addGlobal: usually the first
  229. *)
  230. PROCEDURE Add* ( CONST path : ARRAY OF CHAR; CONST name : ARRAY OF CHAR; addGlobal : BOOLEAN; VAR res : WORD );
  231. VAR
  232. file, entryfile, tmp, tmp2 : Files.FileName;
  233. res1: WORD; time, date, size : LONGINT;
  234. w : Files.Writer;
  235. flags : SET;
  236. enum : Files.Enumerator;
  237. urlRepo : Strings.String;
  238. BEGIN
  239. res := SVNOutput.ResOK;
  240. SetPath ( path, res1 );
  241. ASSERT ( res1 = SVNOutput.ResOK );
  242. ASSERT ( ~pathIsFile );
  243. IF IsItemVersioned ( name ) THEN
  244. res := SVNOutput.ResALREADYVERSIONED;
  245. RETURN;
  246. END;
  247. Files.JoinPath ( path, name, file );
  248. ASSERT ( ~SVNUtil.FileExists ( file ) ); (* 'name' must be a directory *)
  249. NEW ( enum );
  250. enum.Open ( file, {} ); (* get all files/dirs in the actual directory *)
  251. Files.JoinPath ( path, ".svn/entries", entryfile );
  252. IF addGlobal THEN
  253. pathLength := Strings.Length(path)+1;
  254. fAdmin := Files.Old ( entryfile );
  255. ASSERT ( fAdmin # NIL );
  256. RemoveFileAttribute2 ( entryfile, fAdmin );
  257. ReadData ( res1 );
  258. fAdmin := Files.Old ( entryfile ); (* needed? *)
  259. Files.OpenWriter ( w, fAdmin, fAdmin.Length() );
  260. WriteAddEntry ( w, name, FALSE );
  261. w.Update;
  262. SetFileAttribute2 ( entryfile, fAdmin );
  263. END;
  264. urlRepo := Strings.Substring2 ( pathLength, file );
  265. w := CreateDummy ( file, urlRepo^ );
  266. (* add all files/dirs in the actual directory *)
  267. WHILE enum.HasMoreEntries() DO
  268. IF enum.GetEntry ( tmp, flags, time, date, size ) THEN
  269. KernelLog.String ( " A " ); KernelLog.String ( tmp ); KernelLog.Ln;
  270. Files.SplitPath ( tmp, tmp, tmp2 );
  271. IF Files.Directory IN flags THEN
  272. Add ( file, tmp2, FALSE, res );
  273. ASSERT ( res = SVNOutput.ResOK );
  274. END;
  275. (* TODO check whether file is already versioned.... hmm can this even happen?? *)
  276. WriteAddEntry ( w, tmp2, ~(Files.Directory IN flags) );
  277. END;
  278. END;
  279. w.Update;
  280. SetFileAttribute2 ( entryfile, fAdmin );
  281. END Add;
  282. (* creates a dummy .svn entry; scheduled for adding *)
  283. PROCEDURE CreateDummy* ( CONST path, urlRepoDir : ARRAY OF CHAR ) : Files.Writer;
  284. VAR
  285. entryfile : Files.FileName;
  286. f : Files.File;
  287. w : Files.Writer;
  288. data : EntryEntity;
  289. BEGIN
  290. CreateDirectory ( path );
  291. Files.JoinPath ( path, ".svn/entries", entryfile );
  292. f := Files.New ( entryfile );
  293. Files.OpenWriter ( w, f, 0 );
  294. data.Schedule := "add";
  295. Files.JoinPath ( adminDir.Url, urlRepoDir, data.Url );
  296. COPY ( adminDir.RepositoryRoot, data.RepositoryRoot );
  297. Write ( w, data );
  298. Files.Register ( f );
  299. RETURN w;
  300. END CreateDummy;
  301. PROCEDURE ReadData* ( VAR res : WORD );
  302. VAR
  303. i : INTEGER;
  304. tmp : ARRAY 256 OF CHAR;
  305. start: ARRAY 2 OF CHAR;
  306. BEGIN
  307. res := SVNOutput.ResOK;
  308. readGlobalData := TRUE; (* now we read global data *)
  309. Files.OpenReader ( r, fAdmin, 0 );
  310. r.Int ( adminDir.Format, FALSE ); r.SkipLn; (* 1 *)
  311. IF adminDir.Format < EntryFileFormat THEN
  312. res := SVNOutput.ResCLIENTOLD;
  313. RETURN;
  314. END;
  315. r.SkipLn; (* 2: name...first entry is empty *)
  316. r.Ln ( adminDir.NodeKind ); (* 3 *)
  317. r.Ln ( tmp ); (* 4 *)
  318. IF tmp # "" THEN
  319. Strings.StrToInt ( tmp, adminDir.Revision ); (* 4 *)
  320. END;
  321. r.Ln ( adminDir.Url ); (* 5 *)
  322. COPY ( adminDir.Url, adminDir.UrlConst );
  323. r.Ln ( adminDir.RepositoryRoot ); (* 6 *)
  324. r.Ln ( adminDir.Schedule ); (* 7 *)
  325. adminDir.GlobalRemoval := (adminDir.Schedule = "delete");
  326. r.SkipLn; (* 8 *)
  327. r.SkipLn; (* 9 *)
  328. r.Ln ( adminDir.LastChangedDate ); (* 10 *)
  329. r.Ln ( tmp ); (* 11 *)
  330. IF tmp # "" THEN
  331. Strings.StrToInt ( tmp, adminDir.LastChangedRevision ); (* 11 *)
  332. END;
  333. r.Ln ( adminDir.LastChangedAuthor ); (* 12 *)
  334. r.SkipLn; (* 13 *)
  335. (* empty line?? *)
  336. r.SkipLn; (* 14 *)
  337. r.Ln ( adminDir.Props ); (* 15 *)
  338. r.Ln ( tmp ); (* 16 *)
  339. start[0] := CHR(12); start[1] := 0X;
  340. IF ~Strings.StartsWith2(start, tmp) THEN
  341. FOR i:=1 TO 10 DO
  342. r.SkipLn; (* 17-27 *)
  343. END;
  344. r.Ln ( adminDir.RepositoryUUID );
  345. r.SkipLn; (* ^L *)
  346. END;
  347. IF pathIsFile THEN (* search for more deatils in entries file... file or directory entry *)
  348. IF ~IsItemVersioned ( name ) THEN
  349. res := SVNOutput.ResNOTVERSIONED;
  350. ELSE
  351. (* found file entry... read data into adminDir *)
  352. ReadFileData ( name, res );
  353. END;
  354. END;
  355. END ReadData;
  356. PROCEDURE ReadFileData ( CONST name : ARRAY OF CHAR; VAR res : WORD );
  357. VAR
  358. tmp : ARRAY 256 OF CHAR;
  359. start: ARRAY 2 OF CHAR;
  360. BEGIN
  361. res := SVNOutput.ResOK;
  362. IF name = "" THEN
  363. r.Ln ( adminDir.Name ); (* 2*)
  364. ELSE
  365. COPY ( name, adminDir.Name );
  366. END;
  367. Strings.Concat ( adminDir.UrlConst, "/", adminDir.Url ); Strings.Append ( adminDir.Url, adminDir.Name );
  368. r.Ln ( adminDir.NodeKind ); (* 3*)
  369. IF r.Peek() # CHR(12) THEN
  370. r.Ln ( tmp ); (* 4*)
  371. IF tmp # "" THEN
  372. Strings.StrToInt ( tmp, adminDir.Revision );
  373. END;
  374. r.SkipLn; r.SkipLn; (* 5-6 *)
  375. r.Ln ( adminDir.Schedule ); (* 7 *)
  376. IF adminDir.Schedule = "" THEN
  377. r.Ln ( adminDir.TextLastUpdated ); (* 8 *)
  378. r.Ln ( adminDir.Checksum ); (* 9 *)
  379. r.Ln ( adminDir.LastChangedDate ); (* 10 *)
  380. r.Ln ( tmp ); (* 11 *)
  381. IF tmp # "" THEN
  382. Strings.StrToInt ( tmp, adminDir.LastChangedRevision );
  383. ELSE
  384. res := SVNOutput.ResNOTVERSIONED;
  385. RETURN;
  386. END;
  387. r.Ln ( adminDir.LastChangedAuthor ); (* 12 *)
  388. END;
  389. END;
  390. (* read all lines until we reach end of entry; including CHR(12) *)
  391. start[0] := CHR(12); start[1] := 0X;
  392. REPEAT
  393. r.Ln ( tmp );
  394. IF r.res # Files.Ok THEN RETURN END;
  395. UNTIL Strings.StartsWith2(start, tmp);
  396. END ReadFileData;
  397. PROCEDURE PrintData*;
  398. BEGIN
  399. context.out.String ("Path: "); context.out.String ( path ); context.out.Ln; context.out.Update;
  400. context.out.String ("URL: ");
  401. context.out.String (adminDir.Url);
  402. context.out.Ln;
  403. context.out.String ("Repository Root: ");
  404. context.out.String (adminDir.RepositoryRoot);
  405. context.out.Ln;
  406. context.out.String ("Repository UUID: ");
  407. context.out.String (adminDir.RepositoryUUID);
  408. context.out.Ln;
  409. context.out.String ("Revision: ");
  410. context.out.Int (adminDir.Revision, 0);
  411. context.out.Ln;
  412. context.out.String ("Node Kind: ");
  413. IF adminDir.NodeKind = "dir" THEN
  414. context.out.String ("directory");
  415. ELSE
  416. context.out.String (adminDir.NodeKind);
  417. END;
  418. context.out.Ln;
  419. context.out.String ( "Schedule: " );
  420. IF (adminDir.Schedule = "") OR (adminDir.Schedule = "normal") THEN
  421. context.out.String ( "normal" );
  422. ELSE
  423. context.out.String ( adminDir.Schedule );
  424. END;
  425. context.out.Ln;
  426. (* if we added a file the rest of the informations aren't important *)
  427. IF adminDir.Schedule = "add" THEN
  428. RETURN;
  429. END;
  430. context.out.String ("Last Changed Author: ");
  431. context.out.String (adminDir.LastChangedAuthor);
  432. context.out.Ln;
  433. context.out.String ("Last Changed Revision: ");
  434. context.out.Int (adminDir.LastChangedRevision, 0);
  435. context.out.Ln;
  436. context.out.String ("Last Changed Date: ");
  437. context.out.String (adminDir.LastChangedDate);
  438. context.out.Ln;
  439. IF adminDir.NodeKind = "file" THEN
  440. context.out.String ("Text Last Updated: ");
  441. context.out.String (adminDir.TextLastUpdated);
  442. context.out.Ln;
  443. context.out.String ("Checksum: ");
  444. context.out.String (adminDir.Checksum);
  445. context.out.Ln;
  446. END;
  447. context.out.Update;
  448. END PrintData;
  449. PROCEDURE ReadWriteLines* ( count : LONGINT );
  450. VAR
  451. i : LONGINT;
  452. tmp : ARRAY 256 OF CHAR;
  453. BEGIN
  454. FOR i := 1 TO count DO
  455. IF r.Peek() = CHR(12) THEN
  456. tmp := "";
  457. ELSE
  458. r.Ln ( tmp );
  459. END;
  460. ASSERT ( r.res = Files.Ok );
  461. w.String ( tmp ); w.Char ( 0AX );
  462. END;
  463. END ReadWriteLines;
  464. PROCEDURE ReadWriteLine* ( VAR str : ARRAY OF CHAR );
  465. BEGIN
  466. IF r.Peek() # CHR(12) THEN
  467. r.Ln ( str );
  468. ELSE
  469. str := "";
  470. END;
  471. w.String ( str );
  472. w.Char ( 0AX );
  473. END ReadWriteLine;
  474. PROCEDURE ReadWriteRest*;
  475. VAR
  476. tmp : ARRAY 256 OF CHAR;
  477. len : LONGINT;
  478. BEGIN
  479. REPEAT
  480. r.Bytes ( tmp, 0, LEN(tmp), len );
  481. w.Bytes ( tmp, 0, len );
  482. UNTIL len < LEN(tmp);
  483. END ReadWriteRest;
  484. PROCEDURE ReadWriteString* ( CONST str : ARRAY OF CHAR );
  485. BEGIN
  486. IF r.Peek() # CHR(12) THEN
  487. r.SkipLn;
  488. END;
  489. w.String ( str );
  490. w.Char ( 0AX );
  491. END ReadWriteString;
  492. (* EOE = end of entry CHR(12) *)
  493. PROCEDURE ReadWriteToEOE*;
  494. VAR
  495. tmp : ARRAY 256 OF CHAR; start: ARRAY 2 OF CHAR;
  496. BEGIN
  497. start[0] := CHR(12); start[1] := 0X;
  498. REPEAT
  499. r.Ln ( tmp );
  500. IF r.res # Files.Ok THEN RETURN END;
  501. w.String ( tmp ); w.Char ( 0AX );
  502. UNTIL Strings.StartsWith2(start, tmp);
  503. END ReadWriteToEOE;
  504. PROCEDURE IsEOF* () : BOOLEAN;
  505. VAR
  506. c : CHAR;
  507. BEGIN
  508. c := r.Peek();
  509. RETURN (r.res # Files.Ok) OR (c = 0X);
  510. END IsEOF;
  511. PROCEDURE WriteUpdate*;
  512. VAR
  513. res : WORD;
  514. overwrite : BOOLEAN;
  515. BEGIN
  516. w.Update;
  517. Files.Register ( fAdminTemp );
  518. overwrite := TRUE;
  519. RemoveFileAttribute ( entriesfile );
  520. Files.CopyFile ( entriesfiletemp, entriesfile, overwrite, res );
  521. SetFileAttribute ( entriesfile );
  522. ASSERT ( res = Files.Ok );
  523. END WriteUpdate;
  524. PROCEDURE ReadWriteEOE*;
  525. BEGIN
  526. SkipReaderToEOE;
  527. w.Char ( CHR(12) ); w.Char ( 0AX );
  528. END ReadWriteEOE;
  529. END Entry;
  530. PROCEDURE WriteAddEntry* ( w : Files.Writer; CONST name : ARRAY OF CHAR; file : BOOLEAN );
  531. VAR
  532. data : EntryEntity;
  533. BEGIN
  534. COPY ( name, data.Name );
  535. data.Schedule := "add";
  536. IF file THEN data.NodeKind := "file" ELSE data.NodeKind := "dir" END;
  537. Write ( w, data );
  538. END WriteAddEntry;
  539. PROCEDURE WriteString ( w : Files.Writer; CONST line : ARRAY OF CHAR );
  540. BEGIN
  541. w.String ( line );
  542. w.Char ( 0AX );
  543. END WriteString;
  544. PROCEDURE WriteInt ( w : Files.Writer; line : LONGINT );
  545. BEGIN
  546. IF line # 0 THEN
  547. w.Int ( line, 0 );
  548. END;
  549. w.Char ( 0AX );
  550. END WriteInt;
  551. PROCEDURE Write* ( w : Files.Writer; data : EntryEntity );
  552. VAR
  553. i : LONGINT;
  554. tmp : ARRAY 34 OF CHAR;
  555. BEGIN
  556. IF data.Name = "" THEN
  557. (* write the header section in .svn/entries *)
  558. WriteInt ( w, EntryFileFormat ); (* 1 *)
  559. w.Char ( 0AX ); (* 2 *)
  560. WriteString ( w, "dir" ); (* 3 *)
  561. IF data.Schedule = "add" THEN
  562. w.String ( "0" ); w.Char ( 0AX ); (* 4 *)
  563. ELSE
  564. WriteInt ( w, data.Revision ); (* 4 *)
  565. END;
  566. WriteString ( w, data.Url ); (* 5 *)
  567. WriteString ( w, data.RepositoryRoot ); (* 6 *)
  568. WriteString ( w, data.Schedule ); (* 7 *)
  569. w.Char ( 0AX ); (* 8 *)
  570. w.Char ( 0AX ); (* 9 *)
  571. WriteString ( w, data.LastChangedDate ); (* 10 *)
  572. WriteInt ( w, data.LastChangedRevision ); (* 11 *)
  573. WriteString ( w, data.LastChangedAuthor ); (* 12 *)
  574. WriteString ( w, data.Props ); (* 13 *)
  575. w.Char ( 0AX ); (* 14 *)
  576. w.String ( "svn:special svn:externals svn:needs-lock" ); w.Char ( 0AX ); (* 15 *)
  577. IF data.Schedule = "" THEN
  578. FOR i := 16 TO 26 DO w.Char ( 0AX ) END;
  579. WriteString ( w, data.RepositoryUUID ); (* 27 *)
  580. END;
  581. ELSIF data.NodeKind = "file" THEN
  582. (* write the file section in .svn/entries *)
  583. WriteString ( w, data.Name ); (* 2 *)
  584. w.String ( "file" ); w.Char ( 0AX ); (* 3 *)
  585. IF data.Schedule = "add" THEN
  586. w.String ( "0" ); w.Char ( 0AX ); (* 4 *)
  587. ELSE
  588. WriteInt ( w, data.Revision ); (* 4 *)
  589. END;
  590. w.Char ( 0AX ); (* 5 *)
  591. w.Char ( 0AX ); (* 6 *)
  592. WriteString ( w, data.Schedule ); (* 7 *)
  593. IF data.Schedule = "" THEN
  594. IF data.TextLastUpdated = "" THEN
  595. (* TODO is this precise enough? maybe we need to get the time from the file directly *)
  596. Strings.FormatDateTime ( SVNOutput.DateFormat, Dates.Now(), tmp );
  597. WriteString ( w, tmp ); (* 8 *)
  598. ELSE
  599. WriteString ( w, data.TextLastUpdated ); (* 8 *)
  600. END;
  601. WriteString ( w, data.Checksum ); (* 9 *)
  602. WriteString ( w, data.LastChangedDate ); (* 10 *)
  603. WriteInt ( w, data.LastChangedRevision ); (* 11 *)
  604. WriteString ( w, data.LastChangedAuthor ); (* 12 *)
  605. (* TODO more parameters.. like has-props.. add them if needed *)
  606. END;
  607. ELSE
  608. (* write the directory section in .svn/entries *)
  609. WriteString ( w, data.Name ); (* 2 *)
  610. w.String ( "dir" ); w.Char ( 0AX ); (* 3 *)
  611. IF data.Schedule # "" THEN
  612. w.Char ( 0AX ); (* 4 *)
  613. w.Char ( 0AX ); (* 5 *)
  614. w.Char ( 0AX ); (* 6 *)
  615. WriteString ( w, data.Schedule ); (* 7 *)
  616. END;
  617. END;
  618. w.Char ( CHR(12) ); w.Char ( 0AX );
  619. w.Update;
  620. END Write;
  621. (* write data to the .svn/all-wcprops file *)
  622. PROCEDURE WriteWCPROPS* ( CONST path, filename, verurl : ARRAY OF CHAR );
  623. CONST
  624. key = "svn:wc:ra_dav:version-url";
  625. VAR
  626. tmp,fstr,fstr2 : ARRAY 256 OF CHAR;
  627. read, len, keyLength, i : LONGINT; res: WORD;
  628. fr, fw : Files.File;
  629. r : Files.Reader;
  630. w : Files.Writer;
  631. overwrite, nextFileEntry, hasDirEntry : BOOLEAN;
  632. BEGIN
  633. keyLength := Strings.Length ( key );
  634. Files.JoinPath ( path, ".svn/all-wcprops", fstr );
  635. Files.JoinPath ( path, ".svn/tmp/od-all-wcprops", fstr2 );
  636. fw := Files.Old ( fstr );
  637. IF fw = NIL THEN
  638. fw := Files.New ( fstr );
  639. Files.Register ( fw );
  640. END;
  641. RemoveFileAttribute2 ( fstr, fw );
  642. overwrite := TRUE;
  643. Files.CopyFile ( fstr, fstr2, overwrite, res );
  644. ASSERT ( res = Files.Ok );
  645. fr := Files.Old ( fstr2 );
  646. ASSERT ( fr # NIL );
  647. Files.OpenWriter ( w, fw, 0 );
  648. Files.OpenReader ( r, fr, 0 );
  649. hasDirEntry := FALSE;
  650. IF filename # "" THEN
  651. (* we have a file, first search for it and then add it at the correct position *)
  652. nextFileEntry := FALSE;
  653. LOOP
  654. r.Ln ( tmp );
  655. IF r.res # Files.Ok THEN
  656. IF ~hasDirEntry THEN
  657. (* make dummy dir entry *)
  658. w.String ( "K " ); w.String ( "0" ); w.Char ( 0AX );
  659. w.String ( "" ); w.Char ( 0AX );
  660. w.String ( "V " ); w.String ( "0" ); w.Char ( 0AX );
  661. w.String ( "" ); w.Char ( 0AX );
  662. w.String ( "END" ); w.Char ( 0AX );
  663. END;
  664. (* didn't find the file..so we add a new one *)
  665. w.String ( filename ); w.Char ( 0AX );
  666. w.Update;
  667. EXIT;
  668. END;
  669. hasDirEntry := TRUE;
  670. w.String ( tmp ); w.Char ( 0AX );
  671. IF nextFileEntry & (tmp = filename) THEN EXIT END;
  672. nextFileEntry := (tmp = "END");
  673. END;
  674. END;
  675. (* directory entry comes at the beginning *)
  676. w.String ( "K " ); w.Int ( keyLength, 0 ); w.Char ( 0AX );
  677. w.String ( key ); w.Char ( 0AX );
  678. w.String ( "V " ); w.Int ( Strings.Length ( verurl ), 0 ); w.Char ( 0AX );
  679. w.String ( verurl ); w.Char ( 0AX );
  680. w.String ( "END" ); w.Char ( 0AX );
  681. (* search the start position of the remaining entries *)
  682. FOR i := 1 TO 5 DO
  683. r.SkipLn;
  684. END;
  685. (* copy the rest *)
  686. read := 0;
  687. LOOP
  688. r.Bytes ( tmp, read, LEN(tmp), len );
  689. w.Bytes ( tmp, read, len );
  690. INC ( read, len );
  691. IF len <= LEN(tmp) THEN EXIT END;
  692. END;
  693. w.Update;
  694. SetFileAttribute2 ( fstr, fw );
  695. END WriteWCPROPS;
  696. PROCEDURE CreateDirectory* ( CONST path : ARRAY OF CHAR );
  697. VAR
  698. tmp, tmp2, svndir : Files.FileName;
  699. res : WORD;
  700. f : Files.File;
  701. w : Files.Writer;
  702. BEGIN
  703. Files.JoinPath ( path, ".svn", svndir ); Files.CreateDirectory ( svndir, res ); ASSERT ( res = 0 );
  704. Files.JoinPath ( svndir, "prop-base", tmp ); Files.CreateDirectory ( tmp, res ); ASSERT ( res = 0 );
  705. Files.JoinPath ( svndir, "props", tmp ); Files.CreateDirectory ( tmp, res ); ASSERT ( res = 0 );
  706. Files.JoinPath ( svndir, "text-base", tmp ); Files.CreateDirectory ( tmp, res ); ASSERT ( res = 0 );
  707. Files.JoinPath ( svndir, "tmp", tmp ); Files.CreateDirectory ( tmp, res ); ASSERT ( res = 0 );
  708. Files.JoinPath ( tmp, "prop-base", tmp2 ); Files.CreateDirectory ( tmp2, res ); ASSERT ( res = 0 );
  709. Files.JoinPath ( tmp, "props", tmp2 ); Files.CreateDirectory ( tmp2, res ); ASSERT ( res = 0 );
  710. Files.JoinPath ( tmp, "text-base", tmp2 ); Files.CreateDirectory ( tmp2, res ); ASSERT ( res = 0 );
  711. Files.JoinPath ( svndir, "format", tmp );
  712. f := Files.New ( tmp );
  713. Files.OpenWriter ( w, f, 0 );
  714. w.Int ( EntryFileFormat, 0 );
  715. w.Char ( 0AX );
  716. w.Update;
  717. Files.Register ( f );
  718. SetFileAttribute ( tmp );
  719. END CreateDirectory;
  720. (* refactor: use SVNAdmin.Read* to get checksum.. *)
  721. PROCEDURE ReadChecksum* ( CONST file : ARRAY OF CHAR ) : Strings.String;
  722. VAR
  723. tmp, path, name : ARRAY 256 OF CHAR;
  724. nextFileEntry : BOOLEAN;
  725. f : Files.File;
  726. r : Files.Reader;
  727. i : LONGINT;
  728. s : Strings.String;
  729. start: ARRAY 2 OF CHAR;
  730. BEGIN
  731. NEW ( s, 34 );
  732. Files.SplitPath ( file, path, name );
  733. IF Strings.EndsWith ( "svn-base", file ) THEN
  734. Files.SplitPath ( path, path, tmp );
  735. Files.SplitPath ( path, path, tmp );
  736. Strings.Truncate ( name, Strings.Length ( name ) - Strings.Length ( ".svn-base" ) );
  737. END;
  738. Files.JoinPath ( path, ".svn/entries", tmp );
  739. f := Files.Old ( tmp );
  740. ASSERT ( f # NIL );
  741. Files.OpenReader ( r, f, 0 );
  742. nextFileEntry := FALSE;
  743. LOOP
  744. r.Ln ( tmp );
  745. IF r.res # Files.Ok THEN RETURN NIL END;
  746. IF nextFileEntry & (tmp = name) THEN
  747. FOR i := 1 TO 6 DO
  748. r.Ln ( tmp );
  749. ASSERT ( r.res = Files.Ok );
  750. END;
  751. r.Ln ( s^ );
  752. RETURN s;
  753. END;
  754. start[0] := CHR(12); start[1] := 0X;
  755. nextFileEntry := Strings.StartsWith ( start, 0, tmp );
  756. END;
  757. END ReadChecksum;
  758. PROCEDURE CheckChecksum* ( CONST file : ARRAY OF CHAR ) : BOOLEAN;
  759. VAR
  760. s, s2 : Strings.String;
  761. BEGIN
  762. s := SVNUtil.GetChecksum ( file );
  763. s2 := ReadChecksum ( file );
  764. RETURN s^ = s2^;
  765. END CheckChecksum;
  766. PROCEDURE Traverse* ( CONST path : ARRAY OF CHAR; handler : TraverseHandler; data : ANY; verurl : BOOLEAN; VAR res : WORD );
  767. VAR
  768. tmp, tmp2 : ARRAY 256 OF CHAR;
  769. adminEntry : Entry;
  770. BEGIN
  771. NEW ( adminEntry, NIL );
  772. adminEntry.SetPath ( path, res );
  773. IF res # SVNOutput.ResOK THEN RETURN END;
  774. adminEntry.ReadData ( res );
  775. IF res # SVNOutput.ResOK THEN RETURN END;
  776. IF verurl & (adminEntry.adminDir.Schedule = "") THEN
  777. adminEntry.ReadVersionUrl ( "" );
  778. END;
  779. IF adminEntry.pathIsFile THEN
  780. Files.SplitPath ( path, tmp, tmp2 );
  781. IF handler ( tmp,adminEntry.adminDir, data ) THEN END;
  782. RETURN;
  783. END;
  784. IF ~handler ( path, adminEntry.adminDir, data ) THEN
  785. RETURN;
  786. END;
  787. WHILE adminEntry.r.Peek() # 0X DO
  788. adminEntry.ReadFileData ( "", res );
  789. ASSERT ( res = SVNOutput.ResOK );
  790. IF adminEntry.adminDir.NodeKind = "dir" THEN
  791. Files.JoinPath ( path, adminEntry.adminDir.Name, tmp );
  792. Traverse ( tmp, handler, data, verurl, res );
  793. ELSE
  794. IF verurl & (adminEntry.adminDir.Schedule = "") THEN
  795. adminEntry.ReadVersionUrl ( adminEntry.adminDir.Name );
  796. END;
  797. IF ~handler ( path, adminEntry.adminDir, data ) THEN
  798. RETURN;
  799. END;
  800. END;
  801. END;
  802. END Traverse;
  803. PROCEDURE CopyToBaseFile* ( CONST file : ARRAY OF CHAR );
  804. VAR
  805. res : WORD;
  806. overwrite : BOOLEAN;
  807. dest, path, name : Files.FileName;
  808. BEGIN
  809. IF SVNUtil.FileExists ( file ) THEN
  810. overwrite := TRUE;
  811. Files.SplitPath ( file, path, name );
  812. Files.JoinPath ( path, ".svn/text-base", path );
  813. Files.JoinPath ( path, name, dest );
  814. Strings.Append ( dest, ".svn-base" );
  815. Kernel32.SetFileAttributes ( dest, {} );
  816. Files.CopyFile ( file, dest, overwrite, res );
  817. Kernel32.SetFileAttributes ( dest, {Files.ReadOnly} );
  818. ASSERT ( res = Files.Ok );
  819. END;
  820. END CopyToBaseFile;
  821. PROCEDURE SetFileAttribute* ( file : ARRAY OF CHAR );
  822. BEGIN
  823. Kernel32.SetFileAttributes ( file, {Files.ReadOnly} );
  824. END SetFileAttribute;
  825. PROCEDURE RemoveFileAttribute* ( file : ARRAY OF CHAR );
  826. BEGIN
  827. Kernel32.SetFileAttributes ( file, {} );
  828. END RemoveFileAttribute;
  829. PROCEDURE SetFileAttribute2* ( file : ARRAY OF CHAR; f : Files.File );
  830. BEGIN
  831. Kernel32.SetFileAttributes ( file, {Files.ReadOnly} );
  832. INCL ( f.flags, Files.ReadOnly );
  833. END SetFileAttribute2;
  834. PROCEDURE RemoveFileAttribute2* ( file : ARRAY OF CHAR; f : Files.File );
  835. BEGIN
  836. Kernel32.SetFileAttributes ( file, {} );
  837. EXCL ( f.flags, Files.ReadOnly );
  838. END RemoveFileAttribute2;
  839. END SVNAdmin.