KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > antmod > scm > impl > SvnSystemImpl


1 package org.antmod.scm.impl;
2
3 import java.io.BufferedReader JavaDoc;
4 import java.io.File JavaDoc;
5 import java.io.IOException JavaDoc;
6 import java.io.StringReader JavaDoc;
7 import java.util.ArrayList JavaDoc;
8 import java.util.Collections JavaDoc;
9 import java.util.StringTokenizer JavaDoc;
10
11 import org.antmod.conf.AntmodProperties;
12 import org.antmod.scm.ScmDifference;
13 import org.antmod.scm.ScmSystem;
14 import org.antmod.scm.ScmUrl;
15 import org.antmod.scm.ScmVersion;
16 import org.antmod.util.ProcessLauncher;
17 import org.apache.commons.io.FileUtils;
18 import org.apache.commons.io.HexDump;
19 import org.apache.commons.lang.StringUtils;
20 import org.apache.tools.ant.BuildException;
21
22 /**
23  * Subversion repository provider, providing access to this
24  * compelling Source Configuration Management alternative for CVS.
25  * <p/>
26  * This Subversion repository provider is a thin layer on top
27  * of the "svn" commandline executable, and as such
28  * requires the "svn" executable to be in the PATH
29  * of the system.
30  *
31  * @author Klaas Waslander
32  */

33 public class SvnSystemImpl implements ScmSystem {
34
35     public final static char REVISION_NAME_SEPARATOR = '_';
36     public final static char REVISION_VERSION_SEPARATOR = '.';
37
38     private ScmUrl url;
39     private String JavaDoc standardOutput;
40     private String JavaDoc errorOutput;
41     
42
43     /**
44      * Public default onstructor.
45      */

46     public SvnSystemImpl() {
47     }
48
49     public String JavaDoc getStandardOutput() {
50         return standardOutput;
51     }
52     public String JavaDoc getErrorOutput() {
53         return errorOutput;
54     }
55
56     /* (non-Javadoc)
57      * @see org.antmod.scm.ScmSystem#getUrl()
58      */

59     public ScmUrl getUrl() {
60         return url;
61     }
62
63     /* (non-Javadoc)
64      * @see org.antmod.scm.ScmSystem#setUrl(org.antmod.scm.ScmUrl)
65      */

66     public void setUrl(ScmUrl providerUrl) {
67         this.url = providerUrl;
68     }
69
70     /**
71      * Imports given file into Subversion, with recursivity either on or off.
72      * @param file The file/directory to be imported
73      * @param recursive Whether to recurse into subdirectories, if any.
74      */

75     private void doImport(File JavaDoc file, boolean recursive) {
76         if (!file.isDirectory()) {
77             throw new IllegalArgumentException JavaDoc("Cannot import a file, it needs to be a directory: " + file.getPath());
78         }
79         ArrayList JavaDoc svnCommand = new ArrayList JavaDoc();
80
81         String JavaDoc creationMessage = "Creation of new module " + file.getName();
82         String JavaDoc svnModulePath = renderUrlToSvnArg(getUrl()) + '/' + file.getName();
83
84         // create import command for adding to repository
85
svnCommand.add("import");
86         addAuthenticationArgs(svnCommand);
87         svnCommand.add("-m");
88         svnCommand.add(creationMessage);
89         if (!recursive) {
90             svnCommand.add("--non-recursive");
91         }
92         svnCommand.add(".");
93         svnCommand.add(svnModulePath + "/trunk");
94
95         // run import command in the same directory
96
run(file, svnCommand);
97
98         // create initial tags and branches directory in repository
99
svnCommand = new ArrayList JavaDoc();
100         svnCommand.add("mkdir");
101         addAuthenticationArgs(svnCommand);
102         svnCommand.add("-m");
103         svnCommand.add(creationMessage);
104         svnCommand.add(svnModulePath + "/branches");
105         run(null, svnCommand);
106
107         svnCommand.set(svnCommand.size() - 1, svnModulePath + "/tags");
108         run(null, svnCommand);
109     }
110
111     public void doAdd(File JavaDoc file, boolean recursive) {
112         // check for parent ".svn" folder
113
File JavaDoc parentDir = file.getParentFile();
114
115         if (!isCheckoutDir(parentDir)) {
116             // silently switch to importing...
117
doImport(file, recursive);
118         } else {
119             // recursively add, 'recursive' flag ignored!
120
ArrayList JavaDoc svnCommand = new ArrayList JavaDoc();
121             svnCommand.add("add");
122             svnCommand.add(file.getName());
123             run(parentDir, svnCommand);
124         }
125     }
126
127
128     /**
129      * Implements Subversion checkout of a module.
130      */

131     public void doCheckout(String JavaDoc moduleName, File JavaDoc destDir, ScmVersion version, boolean reallyQuiet) {
132         doSvnRetrieve("checkout", moduleName, destDir, version, reallyQuiet);
133     }
134
135     /**
136      * Implements Subversion export of a module.
137      */

138     public void doExport(String JavaDoc moduleName, File JavaDoc destDir, ScmVersion version, boolean reallyQuiet) {
139         doSvnRetrieve("export", moduleName, destDir, version, reallyQuiet);
140     }
141
142     public void doCheckoutOrUpdate(String JavaDoc packageName, File JavaDoc destDir, ScmVersion version, boolean reallyQuiet) {
143         if (isCheckoutDir(destDir)) {
144             doUpdate(packageName, destDir, version, reallyQuiet);
145         } else {
146             doCheckout(packageName, destDir, version, reallyQuiet);
147         }
148     }
149
150     private void doSvnRetrieve(String JavaDoc svnCommandName, String JavaDoc moduleName, File JavaDoc destDir, ScmVersion version, boolean reallyQuiet) {
151         if (destDir == null) {
152             throw new IllegalArgumentException JavaDoc("destDir attribute for Subversion " + svnCommandName + " must not be null");
153         }
154         if (destDir.getParentFile() == null || !destDir.getParentFile().exists()) {
155             throw new IllegalArgumentException JavaDoc("destDir parent directory (basedir for the " +svnCommandName + ") for Subversion " + svnCommandName + " does not exist on filesystem: " + destDir.getParentFile());
156         }
157
158         ArrayList JavaDoc svnCommand = new ArrayList JavaDoc();
159         svnCommand.add(svnCommandName);
160
161         // add authentication tokens...
162
addAuthenticationArgs(svnCommand);
163
164         // checkout proper module and version
165
svnCommand.add(renderUrlToSvnArg(getUrl()) + '/' + renderSvnPath(moduleName, version));
166
167         // checkout in the proper target directory locally
168
svnCommand.add(destDir.getName());
169
170         boolean suppressErrorOutput = reallyQuiet;
171         boolean suppressStandardOutput = reallyQuiet;
172         //String output =
173
run(destDir.getParentFile(), svnCommand, suppressErrorOutput, suppressStandardOutput);
174         /*
175         if (!reallyQuiet && !StringUtils.isBlank(output)) {
176             System.err.println(output.trim());
177             this.standardOutput = null;
178         }
179         */

180         if (!suppressStandardOutput) {
181             this.standardOutput = null;
182         }
183     }
184
185     /**
186      * Internal utility to add username, password and non-interactive flags
187      * to the svn commandline in the given stringbuffer.
188      */

189     private void addAuthenticationArgs(ArrayList JavaDoc list) {
190         // add authentication parameters if any...
191
if (getUrl().getUser() != null) {
192             list.add("--username");
193             list.add(getUrl().getUser());
194         }
195         if (getUrl().getPassword() != null) {
196             list.add("--password");
197             list.add(getUrl().getPassword());
198
199             // non-interactive option of svn checkout command, only if password is known!!!
200
list.add("--non-interactive");
201         }
202
203     }
204
205
206     public void doMerge(File JavaDoc moduleDir, ScmVersion version) {
207         doMerge(moduleDir, version, false);
208     }
209     public void doMerge(File JavaDoc moduleDir, ScmVersion version, boolean reallyQuiet) {
210         if (moduleDir == null) {
211             throw new IllegalArgumentException JavaDoc("moduleDir attribute for Subversion merge must not be null");
212         }
213         if (!moduleDir.exists()) {
214             throw new IllegalArgumentException JavaDoc("moduleDir for Subversion merge does not exist on filesystem: " + moduleDir.getPath());
215         }
216         if (!isCheckoutDir(moduleDir)) {
217             throw new IllegalArgumentException JavaDoc("Directory " + moduleDir.getPath() + " is not an existing Subversion checkout.");
218         }
219         ScmVersion localVersion = getLocalVersion(moduleDir);
220
221         ArrayList JavaDoc svnCommand = new ArrayList JavaDoc();
222         svnCommand.add("merge");
223
224         // add authentication tokens...
225
addAuthenticationArgs(svnCommand);
226
227         // add the two versions which are different
228
svnCommand.add(renderUrlToSvnArg(getUrl()) + '/' + renderSvnPath(moduleDir.getName(), localVersion));
229         svnCommand.add(renderUrlToSvnArg(getUrl()) + '/' + renderSvnPath(moduleDir.getName(), version));
230
231         // run the merge
232
boolean suppressErrorOutput = reallyQuiet;
233         boolean suppressStandardOutput = reallyQuiet;
234         run(moduleDir, svnCommand, suppressErrorOutput, suppressStandardOutput);
235         if (!suppressStandardOutput) {
236             this.standardOutput = null;
237         }
238     }
239
240
241     public void doUpdate(File JavaDoc file, ScmVersion version) {
242         doUpdate(file.getName(), file, version, false);
243     }
244     /** this method not yet exposed yet */
245     private void doUpdate(String JavaDoc packageName, File JavaDoc file, ScmVersion version, boolean reallyQuiet) {
246         if (file == null) {
247             throw new IllegalArgumentException JavaDoc("file attribute for Subversion update must not be null");
248         }
249         if (!file.exists()) {
250             throw new IllegalArgumentException JavaDoc("file for Subversion update does not exist on filesystem: " + file.getPath());
251         }
252         if (!isCheckoutDir(file)) {
253             throw new IllegalArgumentException JavaDoc("Directory " + file.getPath() + " is not an existing Subversion checkout.");
254         }
255
256         // check whether the proper SVN url is checked out currently...
257
String JavaDoc currentCheckoutUrl = readCheckoutUrl(file);
258         boolean differentUrl = false;
259         if (currentCheckoutUrl != null &&
260                 (currentCheckoutUrl.indexOf(this.url.getPath()) < 0 ||
261                     (this.url.getHost() != null && currentCheckoutUrl.indexOf(this.url.getHost()) < 0))) {
262             differentUrl = true;
263         }
264
265         // check whether local version changed
266
ScmVersion localVersion = null;
267         boolean differentVersion = false;
268         if (version != null) {
269             localVersion = getLocalVersion(version.getModuleName(), file);
270             if (localVersion == null) {
271                 throw new IllegalStateException JavaDoc("Local version of \"" + file.getName() + "\" unknown! Most likely it is an invalid Subversion checkout.");
272             }
273             if (!version.equals(localVersion)) {
274                 differentVersion = true;
275             }
276         }
277
278         // switch to proper version or url if needed
279
if (differentVersion || differentUrl) {
280             // if no local changes, remove existing checkout and replace with new one
281
String JavaDoc localSvnChanges = getLocalSvnChanges(file);
282             if (!StringUtils.isBlank(localSvnChanges)) {
283                 if (differentVersion) {
284                     System.err.println("Subversion update from version " + localVersion + " to " + version + " not possible because of local changes:");
285                 } else {
286                     System.err.println("Subversion update from url " + currentCheckoutUrl + " to " + renderUrlToSvnArg(this.url) + " not possible because of local changes:");
287                 }
288                 System.err.println(localSvnChanges);
289                 this.errorOutput = null;
290                 return;
291             } else {
292                 if (differentVersion) {
293                     System.err.println("Changing existing checkout of \"" + file.getName() + "\" from version \"" + localVersion + "\" to \"" + version + "\"");
294                 } else {
295                     System.err.println("Changing existing checkout of \"" + file.getName() + "\" from url \"" + currentCheckoutUrl + "\" to \"" + renderUrlToSvnArg(this.url) + "\"");
296                 }
297
298                 // remove existing checkout, by cleaning module directory
299
deleteSvnFilesInDirectory(file);
300
301                 // checkout new version
302
doCheckout(packageName, file, version, reallyQuiet);
303                 return;
304             }
305         }
306
307         // continue with svn update
308
ArrayList JavaDoc svnCommand = new ArrayList JavaDoc();
309         svnCommand.add("update");
310
311         // add authentication tokens...
312
addAuthenticationArgs(svnCommand);
313
314         boolean suppressErrorOutput = reallyQuiet;
315         boolean suppressStandardOutput = reallyQuiet;
316         if (file.isDirectory()) {
317             run(file, svnCommand, suppressErrorOutput, suppressStandardOutput);
318         } else {
319             svnCommand.add(file.getName());
320             run(file.getParentFile(), svnCommand, suppressErrorOutput, suppressStandardOutput);
321         }
322         /*
323         if (!reallyQuiet && !StringUtils.isBlank(resultOutput)) {
324             System.err.println(resultOutput.trim());
325             this.standardOutput = null;
326         }
327         */

328         if (!suppressStandardOutput) {
329             this.standardOutput = null;
330         }
331     }
332
333     private void deleteSvnFilesInDirectory(File JavaDoc dir) {
334         // list svn files
335
ArrayList JavaDoc svnCommand = new ArrayList JavaDoc(1);
336         svnCommand.add("list");
337         String JavaDoc svnResult = run(dir, svnCommand);
338         StringTokenizer JavaDoc st = new StringTokenizer JavaDoc(svnResult, "\n");
339         while (st.hasMoreTokens()) {
340             File JavaDoc moduleFile = new File JavaDoc(dir, st.nextToken().trim());
341             if (moduleFile.exists()) {
342                 if (moduleFile.isDirectory()) {
343                     try {
344                         FileUtils.deleteDirectory(moduleFile);
345                     } catch (IOException JavaDoc e) {
346                         e.printStackTrace();
347                     }
348                 } else {
349                     moduleFile.delete();
350                 }
351             }
352         }
353
354         // delete the ".svn" folder
355
try {
356             FileUtils.deleteDirectory(new File JavaDoc(dir, ".svn"));
357         } catch (IOException JavaDoc e) {
358             e.printStackTrace();
359         }
360     }
361
362
363     /**
364      * Commit the given file or a while directory to Subversion.
365      * @param file
366      */

367     public void doCommit(File JavaDoc file, String JavaDoc message) {
368         if (file == null) {
369             throw new IllegalArgumentException JavaDoc("file attribute for Subversion commit must not be null");
370         }
371         if (message == null) {
372             throw new IllegalArgumentException JavaDoc("message attribute for Subversion commit must not be null");
373         }
374         
375         if (!isCheckoutDir(file)) {
376             System.err.println("Nothing to commit in \"" + file.getPath() + "\", it is not an svn working copy.");
377             return;
378         }
379
380         ArrayList JavaDoc svnCommand = new ArrayList JavaDoc();
381         svnCommand.add("commit");
382
383         // add authentication arguments...
384
addAuthenticationArgs(svnCommand);
385         
386         // message...
387
svnCommand.add("-m");
388         svnCommand.add(message);
389
390         // commit either whole directory, or just one file.
391
if (file.isDirectory()) {
392             run(file, svnCommand);
393         } else {
394             svnCommand.add(file.getName());
395             run(file.getParentFile(), svnCommand);
396         }
397     }
398
399
400     /**
401      * Returns the latest file revision.
402      */

403     public String JavaDoc getRevisionNumber(File JavaDoc file) {
404         if (file.isDirectory()) {
405             throw new IllegalArgumentException JavaDoc("File for 'getRevisionNumber' is a directory: " +file);
406         }
407
408         ArrayList JavaDoc svnCommand = new ArrayList JavaDoc(1);
409         svnCommand.add("info");
410         svnCommand.add(file.getName());
411         String JavaDoc info = run(file.getParentFile(), svnCommand, true);
412         String JavaDoc searchKey = "Last Changed Rev:";
413         int index = info.indexOf(searchKey);
414         if (index > 0) {
415             int endIndex = info.indexOf("\n", index);
416             if (endIndex > 0) {
417                 return info.substring(index + searchKey.length(), endIndex).trim();
418             }
419         }
420         return null;
421     }
422
423     /**
424      * If the given module directory is not a tag, returns the latest version for that directory.
425      * @return null if no latest version is found
426      */

427     public ScmVersion getLatestVersion(File JavaDoc moduleDir) {
428         ScmVersion localVersion = getLocalVersion(moduleDir.getName(), moduleDir);
429         if (localVersion == null) {
430             return null;
431         }
432         if (localVersion.isTag()) {
433             throw new RuntimeException JavaDoc("getLatestVersion is not possible on a Subversion tag.");
434         }
435
436         if (!new File JavaDoc(moduleDir, "module.xml").exists()) {
437             throw new RuntimeException JavaDoc("FATAL: File module.xml does not exist in the module " + moduleDir.getName() + " !!!");
438         }
439
440         ScmVersion[] revs = getVersionsInBranch(new File JavaDoc(moduleDir, "module.xml"), localVersion);
441         ScmVersion latestRev = null;
442         if (revs.length > 0) {
443             return revs[0];
444         } else {
445             return null;
446         }
447     }
448
449     /**
450      * Returns the currently checked out version of the module in the given directory.
451      * @param moduleDir The directory where the module is currently checked out
452      * @return The current local version of the module
453      * @throws org.apache.tools.ant.BuildException
454      */

455     public ScmVersion getLocalVersion(File JavaDoc moduleDir) throws BuildException {
456         return getLocalVersion(moduleDir.getName(), moduleDir);
457     }
458
459     public ScmVersion getLocalVersion(String JavaDoc moduleName, File JavaDoc moduleDir) throws BuildException {
460         if (moduleName == null) {
461             moduleName = moduleDir.getName();
462         }
463         String JavaDoc checkoutUrl = readCheckoutUrl(moduleDir);
464         if (checkoutUrl != null) {
465             return parseSvnPath(moduleName, checkoutUrl);
466         } else {
467             return null;
468         }
469     }
470
471     /**
472      * Reads the current Subversion checkout URL where the given module directory
473      * was checked out from.
474      */

475     private String JavaDoc readCheckoutUrl(File JavaDoc moduleDir) {
476         File JavaDoc svnDir = new File JavaDoc(moduleDir, ".svn");
477         if (!svnDir.exists()) {
478             return null;
479         }
480
481         /* OLD WAY OF RETRIEVING URL, USING SLOW "svn info" COMMAND
482         ArrayList svnCommand = new ArrayList(1);
483         svnCommand.add("info");
484         String info = run(moduleDir, svnCommand);
485         int urlIndex = info.indexOf("URL:");
486         if (urlIndex > 0) {
487             int endOfUrl = info.indexOf("\n", urlIndex);
488             if (endOfUrl > 0) {
489                 return info.substring(urlIndex + 4, endOfUrl).trim();
490             }
491         }
492         */

493         try {
494             String JavaDoc entries = FileUtils.readFileToString(new File JavaDoc(svnDir, "entries"), System.getProperty("file.encoding"));
495             
496             if (entries.startsWith("<?xml")) {
497                 // Subversion 1.3 or earler: xml format...
498
int urlIndex = entries.indexOf("url=\"");
499                 if (urlIndex > 0) {
500                     entries = entries.substring(urlIndex + 5);
501                     int endOfUrl = entries.indexOf("\"");
502                     if (endOfUrl > 0) {
503                         return entries.substring(0, endOfUrl);
504                     }
505                 }
506             } else {
507                 // Subversion 1.4+ : uses non-XML format...
508
int urlIndex = -1;
509                 for (int fifthLine = 4; fifthLine-- > 0;) {
510                     urlIndex = entries.indexOf("\n", urlIndex + 1);
511                     if (urlIndex < 0) {
512                         return null;
513                     }
514                 }
515                 // we arrived at the fifth line!
516
int endOfUrl = entries.indexOf("\n", urlIndex + 1);
517                 if (endOfUrl > 0) {
518                     return entries.substring(urlIndex + 1, endOfUrl).trim();
519                 }
520             }
521         } catch (IOException JavaDoc e) {
522             e.printStackTrace();
523         }
524         return null;
525     }
526
527     /**
528      * Returns all available versions for the given file in the given branch,
529      * with the newest number first and the oldest number last (oldest is usually the ".0" version).
530      */

531     public ScmVersion[] getVersionsInBranch(File JavaDoc file, ScmVersion branch) {
532         if (file == null) {
533             throw new IllegalArgumentException JavaDoc("File attribute for Subversion 'getVersionsInBranch' must not be null");
534         }
535         File JavaDoc moduleDir = file.getParentFile();
536         String JavaDoc moduleName = moduleDir.getName();
537
538         ArrayList JavaDoc svnCommand = new ArrayList JavaDoc();
539         svnCommand.add("list");
540         addAuthenticationArgs(svnCommand);
541
542         // list all tags or branches and filter the ones we want to return
543
String JavaDoc modulePath = renderUrlToSvnArg(getUrl()) + '/' + moduleName;
544         boolean returnTags = false;
545         if (branch == null || branch.isTrunk()) {
546             // get 2 digit versions in 'branches'
547
modulePath += "/branches";
548         } else {
549             // get 3 digit versions in 'tags'
550
modulePath += "/tags";
551             returnTags = true;
552         }
553         svnCommand.add(modulePath);
554
555         ArrayList JavaDoc result = new ArrayList JavaDoc();
556
557         //
558
// get svn output and PARSE it
559
//
560
String JavaDoc svnResult = run(null, svnCommand);
561         StringTokenizer JavaDoc st = new StringTokenizer JavaDoc(svnResult, "\n");
562         String JavaDoc branchVersionString = branch.toString(REVISION_VERSION_SEPARATOR);
563         String JavaDoc versionString;
564         while (st.hasMoreTokens()) {
565             versionString = st.nextToken().trim();
566             if (versionString.endsWith("/")) {
567                 versionString = versionString.substring(0, versionString.length() - 1);
568             }
569             if (returnTags) {
570                 if (versionString.startsWith(branchVersionString) && countVersionChars(REVISION_VERSION_SEPARATOR, versionString) == 2) {
571                     result.add(new ScmVersion(moduleName, versionString));
572                 }
573             } else {
574                 if (countVersionChars(REVISION_VERSION_SEPARATOR, versionString) == 1) {
575                     result.add(new ScmVersion(moduleName, versionString));
576                 }
577             }
578         }
579
580         // sort and return array
581
Collections.sort(result);
582         ScmVersion[] array = new ScmVersion[result.size()];
583         result.toArray(array);
584         return array;
585     }
586
587     /**
588      * Check whether the given checkout directory is up-to-date
589      * when comparing it to the repository contents.
590      * @param checkoutDir The directory with locally checked out contents
591      * @return Whether the checkoutDir is up-to-date
592      */

593     public boolean isUpToDate(File JavaDoc checkoutDir) {
594         return StringUtils.isBlank(getLocalSvnChanges(checkoutDir));
595     }
596     
597     private String JavaDoc getLocalSvnChanges(File JavaDoc checkoutDir) {
598         ArrayList JavaDoc svnCommand = new ArrayList JavaDoc(2);
599         svnCommand.add("status");
600         svnCommand.add("-q");
601         return run(checkoutDir, svnCommand);
602     }
603
604     /**
605      * Implementation of the ScmDifference interface for returning cvs diff entries in this class.
606      * @author Klaas Waslander
607      */

608     private static class ScmDifferenceImpl implements ScmDifference {
609         String JavaDoc filename;
610         boolean conflict = false;
611         StringBuffer JavaDoc log = new StringBuffer JavaDoc();
612
613         ScmDifferenceImpl(String JavaDoc filename) {
614             this.filename = filename;
615         }
616         public String JavaDoc getFilename() {
617             return filename;
618         }
619         public boolean hasConflict() {
620             return conflict;
621         }
622         private void setConflict(boolean conflict) {
623             this.conflict = conflict;
624         }
625         public String JavaDoc getLog() {
626             return log.toString();
627         }
628         private void addLogLine(String JavaDoc logLine) {
629             this.log.append(logLine);
630             this.log.append(HexDump.EOL);
631         }
632     }
633
634     /**
635      * Returns the files that have changed between the two given Subversion versions.
636      */

637     public ScmDifference[] getDifferences(ScmVersion version1, ScmVersion version2) {
638         if (version1 .getModuleName() == null) {
639             throw new IllegalArgumentException JavaDoc("Modulename attribute (of version1) for svndiffs must not be null");
640         }
641         if (version2.getModuleName() == null) {
642             throw new IllegalArgumentException JavaDoc("Modulename attribute (of version2) for svndiffs must not be null");
643         }
644         if (!version1.getModuleName().equals(version2.getModuleName())) {
645             throw new IllegalArgumentException JavaDoc("Module of version1 \"" + version1.getModuleName() + "\" is not the same as module of version2 \"" + version2.getModuleName() + "\"");
646         }
647
648         // build Subversion commandline
649
ArrayList JavaDoc svnCommand = new ArrayList JavaDoc();
650         svnCommand.add("diff");
651         addAuthenticationArgs(svnCommand);
652         svnCommand.add(renderUrlToSvnArg(getUrl()) + '/' + renderSvnPath(version1.getModuleName(), version1));
653         svnCommand.add(renderUrlToSvnArg(getUrl()) + '/' + renderSvnPath(version2.getModuleName(), version2));
654
655         ArrayList JavaDoc result = new ArrayList JavaDoc();
656
657         //
658
// get Subversion output and PARSE it
659
//
660
String JavaDoc svnResult = run(null, svnCommand);
661         try {
662             BufferedReader JavaDoc reader = new BufferedReader JavaDoc(new StringReader JavaDoc(svnResult));
663             String JavaDoc line;
664             ScmDifferenceImpl currentEntry = null;
665
666             while ((line = reader.readLine()) != null) {
667                 if (line.startsWith("Index:")) {
668                     // strip off "Index:"
669
String JavaDoc file = line.substring(7).trim();
670                     // add diff entry object
671
currentEntry = new ScmDifferenceImpl(file);
672                     result.add(currentEntry);
673
674                 } else if (currentEntry != null) {
675                     currentEntry.addLogLine(line);
676
677                     if (line.startsWith("! ")) {
678                         currentEntry.setConflict(true);
679                     }
680                 }
681             }
682         } catch (IOException JavaDoc ioe) {
683             ioe.printStackTrace();
684         }
685
686         ScmDifference[] array = new ScmDifference[result.size()];
687         result.toArray(array);
688         return array;
689     }
690
691     /**
692      * Creates a new branch in the trunk of the given module.
693      */

694     public String JavaDoc createBranchInTrunk(ScmVersion newBranchForModule) {
695         if (newBranchForModule == null) {
696             throw new IllegalArgumentException JavaDoc("newBranchForModule attribute for createBranchInTrunk must not be null");
697         }
698         ArrayList JavaDoc svnCommand = new ArrayList JavaDoc();
699         svnCommand.add("copy");
700         addAuthenticationArgs(svnCommand);
701
702         // message
703
svnCommand.add("-m");
704         svnCommand.add("Create branch " + newBranchForModule);
705
706         // from trunk
707
svnCommand.add(renderUrlToSvnArg(getUrl()) + '/' + renderSvnPath(newBranchForModule.getModuleName(), null));
708
709         // to new branch
710
svnCommand.add(renderUrlToSvnArg(getUrl()) + '/' + renderSvnPath(null, newBranchForModule));
711
712         return run(null, svnCommand);
713     }
714
715     /**
716      * Creates a new tag in the given BRANCH of the given module.
717      */

718     public String JavaDoc createTagInBranch(ScmVersion existingBranch, ScmVersion newTag) {
719         if (existingBranch == null) {
720             throw new IllegalArgumentException JavaDoc("existingBranch attribute for createTagInBranch must not be null");
721         }
722         if (newTag == null) {
723             throw new IllegalArgumentException JavaDoc("newTag attribute for createTagInBranch must not be null");
724         }
725         if (existingBranch.getModuleName() == null) {
726             throw new IllegalArgumentException JavaDoc("moduleName of existing branch for createTagInBranch must not be null");
727         }
728
729         ArrayList JavaDoc svnCommand = new ArrayList JavaDoc();
730         svnCommand.add("copy");
731         addAuthenticationArgs(svnCommand);
732
733         // message
734
svnCommand.add("-m");
735         svnCommand.add("Create tag " + newTag + " in branch " + existingBranch);
736
737         // from branch
738
svnCommand.add(renderUrlToSvnArg(getUrl()) + '/' + renderSvnPath(null, existingBranch));
739
740         // to new tag
741
svnCommand.add(renderUrlToSvnArg(getUrl()) + '/' + renderSvnPath(null, newTag));
742
743         return run(null, svnCommand);
744     }
745
746
747     /**
748      * Utility method for counting the number of given characters in the given string.
749      */

750     private static int countVersionChars(char c, String JavaDoc s) {
751         // count the occurences of char c in string s
752
return StringUtils.countMatches(s, String.valueOf(c));
753     }
754
755
756     /**
757      * Runs the given svn command in the given directory, and returns standard output.
758      * @param baseDir The directory where the command should run
759      * @param commandList The command arguments (passed as arguments to the actual svn command)
760      * @return The standard output, if any
761      */

762     private String JavaDoc run(File JavaDoc baseDir, ArrayList JavaDoc commandList) {
763         return run(baseDir, commandList, false);
764     }
765     
766     private String JavaDoc run(File JavaDoc baseDir, ArrayList JavaDoc commandList, boolean suppressErrorOutput) {
767         return run(baseDir, commandList, suppressErrorOutput, true);
768     }
769
770     private String JavaDoc run(File JavaDoc baseDir, ArrayList JavaDoc commandList, boolean suppressErrorOutput, boolean suppressStandardOutput) {
771         return run(baseDir, commandList, suppressErrorOutput, suppressStandardOutput, false);
772     }
773
774     private String JavaDoc run(File JavaDoc baseDir, ArrayList JavaDoc commandList, boolean suppressErrorOutput, boolean suppressStandardOutput, boolean cleaningUp) {
775         // work with copy for local changes
776
commandList = new ArrayList JavaDoc(commandList);
777         ArrayList JavaDoc orgCommandList = new ArrayList JavaDoc(commandList);
778
779         // make it quiet if not done already
780
String JavaDoc cmd = (String JavaDoc)commandList.get(0);
781         //System.err.println("SVN COMMAND: " + cmd);
782
if (!cmd.equals("info") &&
783                 !cmd.equals("list") &&
784                 !cmd.equals("diff") &&
785                 !cmd.equals("cleanup") &&
786                 !cmd.equals("merge") &&
787                 !cmd.equals("update") &&
788                 !cmd.equals("checkout") &&
789                 !commandList.contains("-q") && !commandList.contains("--quiet")) {
790             commandList.add("-q");
791         }
792
793         // of course, prepend the "svn" executable to invoke
794
commandList.add(0, AntmodProperties.getProperty("antmod.scm.svn.executable"));
795
796         // create launcher
797
ProcessLauncher launcher = new ProcessLauncher(commandList, baseDir);
798
799         final boolean suppressStd = suppressStandardOutput;
800         final StringBuffer JavaDoc stdOut = new StringBuffer JavaDoc();
801         final StringBuffer JavaDoc stdErr = new StringBuffer JavaDoc();
802         stdOut.setLength(0);
803         stdErr.setLength(0);
804         launcher.addOutputListener(new ProcessLauncher.OutputListener() {
805             public void standardOutput(char[] output) {
806                 stdOut.append(output);
807                 
808                 if (!suppressStd) {
809                     System.err.print(output);
810                 }
811             }
812
813             public void errorOutput(char[] output) {
814                 stdErr.append(output);
815             }
816         });
817
818         // launch and wait until done...
819
launcher.launch();
820
821         // detect whether to try "svn cleanup" once
822
if (!cleaningUp && stdErr.length() > 0 && !cmd.equals("cleanup") && stdErr.indexOf("locked") > 0) {
823             System.err.println("Removing lock from \"" + baseDir.getName() + "\" using 'svn cleanup'...");
824             ArrayList JavaDoc cleanupCommand = new ArrayList JavaDoc();
825             cleanupCommand.add("cleanup");
826             run(baseDir, cleanupCommand, false);
827             
828             // try one more time now!
829
return run(baseDir, orgCommandList, suppressErrorOutput, suppressStandardOutput, true);
830         }
831
832         // if there is error output, log it for now
833
if (!suppressErrorOutput && stdErr.length() > 0) {
834             System.err.println("'" + launcher.getCommandLine() + "' produced error output:");
835             System.err.println(stdErr.toString());
836         }
837
838         // return standard output
839
this.standardOutput = stdOut.toString();
840         this.errorOutput = stdErr.toString();
841         return stdOut.toString();
842     }
843
844
845     static ScmVersion parseSvnPath(String JavaDoc moduleName, String JavaDoc svnPath) {
846         // find modulename in url
847
String JavaDoc moduleNameSearch = '/' + moduleName + '/';
848         int moduleNameIndex = svnPath.lastIndexOf(moduleNameSearch);
849         if (moduleNameIndex < 0) {
850             throw new IllegalArgumentException JavaDoc("Subversion URL \"" + svnPath + "\" does not contain modulename \"" + moduleName + "\"");
851         }
852
853         // extract version
854
String JavaDoc versionString = svnPath.substring(moduleNameIndex + moduleNameSearch.length());
855         if (!versionString.startsWith("trunk")) {
856             int nextSlash = versionString.indexOf("/");
857             if (nextSlash < 0) {
858                 throw new IllegalArgumentException JavaDoc("Subversion URL \"" + svnPath + "\" does not have version in URL after tags/branches part.");
859             }
860             versionString = versionString.substring(nextSlash + 1);
861         }
862         return new ScmVersion(moduleName, versionString);
863     }
864
865     static String JavaDoc renderSvnPath(String JavaDoc moduleName, ScmVersion ver) {
866         if (StringUtils.isBlank(moduleName) && StringUtils.isBlank(ver.getModuleName())) {
867             throw new RuntimeException JavaDoc("Modulename unknown - proper Subversion repository path not possible.");
868         }
869         if (moduleName == null) {
870             moduleName = ver.getModuleName();
871         }
872
873         if (ver == null) {
874             return moduleName + "/trunk";
875         }
876
877         if (ver.isTag()) {
878             return moduleName + "/tags/" + ver.toString(REVISION_VERSION_SEPARATOR);
879         } else if (ver.isBranch()) {
880             return moduleName + "/branches/" + ver.toString(REVISION_VERSION_SEPARATOR);
881         } else {
882             return moduleName + "/trunk";
883         }
884     }
885
886     static String JavaDoc renderUrlToSvnArg(ScmUrl url) {
887         StringBuffer JavaDoc result = new StringBuffer JavaDoc();
888         result.append(url.getProtocol());
889         result.append("://");
890         if (url.getProtocol().equals("file")) {
891             result.append('/');
892         } else {
893             result.append(url.getHost());
894         }
895         result.append(url.getPath());
896         return result.toString();
897     }
898
899     public boolean isCheckoutDir(File JavaDoc directory) {
900         return new File JavaDoc(directory, ".svn").exists();
901     }
902 }
903
Popular Tags