KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > antmod > tasks > MergeToBranchTask


1 package org.antmod.tasks;
2
3 import java.io.File JavaDoc;
4 import java.io.IOException JavaDoc;
5
6 import org.antmod.conf.AntmodProperties;
7 import org.antmod.descriptor.DescriptorStoreFactory;
8 import org.antmod.descriptor.ReleaseDescriptor;
9 import org.antmod.scm.ScmDifference;
10 import org.antmod.scm.ScmSystem;
11 import org.antmod.scm.ScmSystemFactory;
12 import org.antmod.scm.ScmVersion;
13 import org.antmod.util.AntUtil;
14 import org.apache.commons.io.FileUtils;
15 import org.apache.commons.io.HexDump;
16 import org.apache.commons.lang.StringUtils;
17 import org.apache.tools.ant.BuildException;
18 import org.apache.tools.ant.Project;
19 import org.apache.tools.ant.Task;
20 import org.apache.tools.ant.types.EnumeratedAttribute;
21
22 /**
23  * Ant task that merges changes from a checked out trunk
24  * to the latest branch or the other way around (but than from <i>any</i> branch you want to the trunk).
25  * <p/>
26  * <strong>Important: this task does not handle multiple merges to the same branch properly yet,
27  * read <a HREF="http://lists.gnu.org/archive/html/info-cvs/2005-01/msg00299.html">this merge question e-mail</a>
28  * for example;
29  * this task would have to create merge-tags to remember from where the last merge happened
30  * to the target trunk/branch. This is needlessly complex, so this task generates a nice
31  * error message that explains to the user how a conflict arising from this can be resolved manually.</strong>
32  * <p/>
33  * This task executes the following steps:
34  * <pre>
35  * (1) if merging to latest branch, check if the trunk has been checked out
36  * (2) if local changes, report+bailout
37  * (3) find target version, if none report+bailout
38  * (4) run diff against target version and report+bailout if conflicts
39  * (5) temp checkout target version into "${antmod.local.dir}" directory
40  * (6) merge checked out version changes to the target version checkout
41  * (7) commit to target version
42  * (8) cleanup temporary checkout of target version under "${antmod.local.dir} directory
43  * </pre>
44  *
45  * @author Klaas Waslander
46  */

47 public class MergeToBranchTask extends Task {
48     private String JavaDoc descriptorName;
49     private String JavaDoc moduleName;
50     private TargetVersionInfo targetVersion;
51     
52
53     /**
54      * Public default constructor.
55      */

56     public MergeToBranchTask() {
57     }
58
59     public void setDescriptor(String JavaDoc descriptorName) {
60         this.descriptorName = descriptorName;
61     }
62
63     public void setModuleName(String JavaDoc moduleName) {
64         if (moduleName != null) {
65             moduleName = moduleName.trim();
66         }
67         this.moduleName = moduleName;
68     }
69     
70     public void setTargetVersion(TargetVersionInfo targetVersion) {
71         this.targetVersion = targetVersion;
72     }
73     
74     private boolean isMergeToTrunk() {
75         return this.targetVersion.getValue().equals(TargetVersionInfo.TRUNK);
76     }
77     
78     /**
79      * Invoked during {@link #execute()} to get a good introduction text
80      * to print to the console at the start of execution.
81      * @return Short one-line introduction text.
82      */

83     protected String JavaDoc getIntroductionText() {
84         if (isMergeToTrunk()) {
85             return "Merge & commit branch changes to the trunk";
86         } else {
87             return "Merge & commit trunk changes to latest branch";
88         }
89     }
90
91     /**
92      * Invoked during {@link #execute()} to ask user for confirmation whether the user really
93      * wants to merge changes to the given branch.
94      * @return Whether the user does not want to proceed.
95      */

96     protected boolean askUserConfirmation(ScmVersion localVersion, ScmVersion targetVersion) {
97         String JavaDoc isOkay = AntUtil.ask(getProject(), "Merge & commit changes from '" + localVersion + "' to '" + targetVersion + "' now? ", "yes,no");
98         return "yes".equalsIgnoreCase(isOkay);
99     }
100     
101     /**
102      * Invoked during {@link #execute()} to commit merged changes
103      * in the given local checkout directory to the ScmSystem
104      */

105     protected void commitMergedChanges(ScmSystem scm, File JavaDoc checkoutDir, ScmVersion localVersion, ScmVersion targetVersion) {
106         logWarning("-- Committing merged changes...");
107         scm.doCommit(checkoutDir, "Antmod " + getTaskName() + " commit from " + ((isMergeToTrunk()) ? "'" + localVersion + "'" : "trunk") + " to " + ((isMergeToTrunk()) ? "trunk" : "latest branch"));
108     }
109
110     /**
111      * Executes the promotion of trunk changes to the latest branch.
112      * @throws BuildException If something goes wrong during promotion to branch
113      */

114     public void execute() throws BuildException {
115         if (this.moduleName == null) {
116             throw new BuildException("FAIL: 'modulename' attribute of " + getTaskName() + "task has to be set.");
117         }
118         if (this.descriptorName == null) {
119             throw new BuildException("FAIL: 'descriptor' attribute of " + getTaskName() + "task has to be set.");
120         }
121         if (this.targetVersion == null) {
122             throw new BuildException("FAIL: 'targetVersion' attribute of " + getTaskName() + "task has to be set.");
123         }
124         logWarning("Intro: " + getIntroductionText());
125         logWarning("");
126
127         // get release descriptor, module and scm system
128
ReleaseDescriptor descriptor = DescriptorStoreFactory.getConfiguredDescriptorStore().getReleaseDescriptor(this.descriptorName);
129         ReleaseDescriptor.Module module = descriptor.getModuleByName(this.moduleName);
130         ScmSystem scm = ScmSystemFactory.getScmSystemByName(module.getRepos());
131
132         // (1) if merging to latest branch, check if the trunk has been checked out
133
ScmVersion localVersion = scm.getLocalVersion(getModuleDir());
134         if (isMergeToTrunk()) {
135             if (localVersion.isTrunk()) {
136                 throw new BuildException(getTaskName() + " FAILED: you do not have a branch of module '"+this.moduleName+"' checked out (trunk is currently checked out).");
137             }
138         } else {
139             if (!localVersion.isTrunk()) {
140                 throw new BuildException(getTaskName() + " FAILED: trunk of module '"+this.moduleName+"' is currently not checked out ("+localVersion+" is currently checked out).");
141             }
142         }
143
144         // (2) if local changes, report+bailout
145
if (!scm.isUpToDate(getModuleDir())) {
146             throw new BuildException(getTaskName() + " FAILED: local checkout of module '"+this.moduleName+"' is NOT up-to-date or has local changes.");
147         }
148
149         // (3) find target version, if none report+bailout
150
ScmVersion targetVersion = null;
151         if (isMergeToTrunk()) {
152             targetVersion = ScmVersion.getTrunkBranch(this.moduleName);
153         } else {
154             targetVersion = scm.getLatestVersion(getModuleDir());
155             if (targetVersion == null) {
156                 throw new BuildException("Could not find a latest branch for module " + this.moduleName);
157             }
158         }
159
160         // (4) run diff against target version and report+bailout if conflicts
161
logWarning("-- Checking for conflicts with " + ((isMergeToTrunk()) ? "'" : "latest branch '") + targetVersion + "'...");
162         ScmDifference[] diffs = scm.getDifferences(targetVersion, localVersion);
163         if (diffs.length == 0) {
164             logWarning("-- Nothing to do - no changes between local '" + localVersion + "' and '"+targetVersion+"'");
165             return;
166         }
167         for (int i = diffs.length; i -- > 0;) {
168             /*
169             logWarning(diffs[i].getFilename() + ":");
170             logWarning(diffs[i].getLog());
171             logWarning("");
172             logWarning("");
173             */

174             if (diffs[i].hasConflict()) {
175                 logWarning("-- CONFLICT in file '" + diffs[i].getFilename() + "':" + HexDump.EOL + diffs[i].getLog());
176                 throw new BuildException(getTaskName() + " FAILED: local '" + localVersion + "' of module '"+this.moduleName+"' has conflict with '" + targetVersion + "' in file '"+diffs[i].getFilename()+"'.");
177             }
178         }
179         logWarning("-- No conflicts were found.");
180         
181         // ask if user really wants this
182
logWarning("");
183         if (!askUserConfirmation(localVersion, targetVersion)) {
184             return;
185         }
186
187         // (5) temp checkout target version into "${antmod.local.dir}" directory
188
File JavaDoc localDir = new File JavaDoc(AntmodProperties.getProperty("antmod.local.dir"));
189         File JavaDoc mtbDir = new File JavaDoc(localDir, getTaskName());
190         File JavaDoc checkoutDir = new File JavaDoc(mtbDir, this.descriptorName + "_" + this.moduleName + "_" + targetVersion + File.separator + this.moduleName);
191         if (checkoutDir.exists()) {
192             try {
193                 FileUtils.deleteDirectory(checkoutDir);
194             } catch (IOException JavaDoc e) {
195                 throw new BuildException(e);
196             }
197         }
198         if (!checkoutDir.mkdirs() && !checkoutDir.exists()) {
199             throw new BuildException(getTaskName() + " FAILED: could not create temp directory '"+checkoutDir+"'");
200         }
201         logWarning("-- Temp checkout " + ((isMergeToTrunk()) ? "'" : "latest branch '") + targetVersion + "' to ${antmod.local.dir}..." );
202         scm.doCheckout(this.moduleName, checkoutDir, targetVersion, true);
203         logWarning("");
204
205         // (6) merge checked out version changes to the target version checkout
206
logWarning("-- Merging '" + localVersion + "' changes to temp checkout of " + ((isMergeToTrunk()) ? "trunk" : "latest branch") + "...");
207         scm.doMerge(checkoutDir, localVersion);
208         if (!StringUtils.isBlank(scm.getErrorOutput())) {
209             logWarning("-- Merging FAILED: " + scm.getErrorOutput());
210             throw new BuildException("Merging failed, checkout " + ((isMergeToTrunk()) ? "the trunk (using 'pickversion') of this module and use 'mergefrombranch'" : "the '" + targetVersion + "' branch (using 'pickversion') of this module and use 'mergefromtrunk'") + " to resolve. This usually happens when '" + getTaskName() + "' is executed twice with the same target branch of the same module.");
211         }
212         logWarning("");
213         
214         // (7) commit to target version
215
commitMergedChanges(scm, checkoutDir, localVersion, targetVersion);
216         logWarning("");
217
218         // (8) cleanup temporary checkout of target version under "${antmod.local.dir} directory
219
try {
220             FileUtils.deleteDirectory(checkoutDir.getParentFile());
221         } catch (IOException JavaDoc e) {
222             throw new BuildException(e);
223         }
224
225         logWarning("-- " + getTaskName() + " completed");
226     }
227
228     /**
229      * Returns the directory of the currently set modulename.
230      */

231     private File JavaDoc getModuleDir() {
232         if (this.moduleName == null) {
233             throw new RuntimeException JavaDoc("Cannot return valid moduledir because 'moduleName' attribute is not set.");
234         }
235         if (new File JavaDoc(getProject().getBaseDir(), AntmodProperties.getProperty("antmod.release.metadata.file")).exists()) {
236             return new File JavaDoc(getProject().getBaseDir(), this.moduleName);
237         } else {
238             return new File JavaDoc(getProject().getBaseDir().getParentFile(), this.moduleName);
239         }
240     }
241
242     /**
243      * Helper method within this class for quickly logging a warning.
244      */

245     protected void logWarning(String JavaDoc msg) {
246         log(msg, Project.MSG_WARN);
247     }
248
249     /**
250      * The target versions that the {@link MergeToBranchTask} Ant task
251      * can handle.
252      *
253      * @author Klaas Waslander
254      */

255     public static class TargetVersionInfo extends EnumeratedAttribute {
256
257         /** Merge changes from the locally checked out trunk to the latest branch of this module. */
258         public static String JavaDoc LATESTBRANCH = "latestbranch";
259
260         /** Merge changes from the locally checked out branch to the trunk of this module. */
261         public static String JavaDoc TRUNK = "trunk";
262
263         private static String JavaDoc[] values =
264                 new String JavaDoc[]{
265                     LATESTBRANCH,
266                     TRUNK
267                 };
268
269         public String JavaDoc[] getValues() {
270             return TargetVersionInfo.values;
271         }
272
273     }
274 }
275
Popular Tags