mirror of https://github.com/jenkinsci/jenkins.git
				
				
				
			Revert "[JENKINS-75465] Delete RunIdMigrator as it has been 10 years since this migration" (#10517)
* Revert "[JENKINS-75465] Delete RunIdMigrator as it has been 10 years since this migration"
* Fast incremental build
* Revert "Fast incremental build"
Leave the commit as only the revert of the original change
This reverts commit 2cf37a11df.
---------
Co-authored-by: Kris Stern <krisstern@outlook.com>
			
			
This commit is contained in:
		
							parent
							
								
									76c461b143
								
							
						
					
					
						commit
						c769824fba
					
				|  | @ -97,6 +97,7 @@ import jenkins.model.JenkinsLocationConfiguration; | |||
| import jenkins.model.ModelObjectWithChildren; | ||||
| import jenkins.model.PeepholePermalink; | ||||
| import jenkins.model.ProjectNamingStrategy; | ||||
| import jenkins.model.RunIdMigrator; | ||||
| import jenkins.model.lazy.LazyBuildMixIn; | ||||
| import jenkins.scm.RunWithSCM; | ||||
| import jenkins.security.HexStringConfidentialKey; | ||||
|  | @ -191,6 +192,10 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R | |||
|     // this should have been DescribableList but now it's too late | ||||
|     protected CopyOnWriteList<JobProperty<? super JobT>> properties = new CopyOnWriteList<>(); | ||||
| 
 | ||||
|     @Restricted(NoExternalUse.class) | ||||
|     @SuppressFBWarnings(value = "PA_PUBLIC_PRIMITIVE_ATTRIBUTE", justification = "Preserve API compatibility") | ||||
|     public transient RunIdMigrator runIdMigrator; | ||||
| 
 | ||||
|     protected Job(ItemGroup parent, String name) { | ||||
|         super(parent, name); | ||||
|     } | ||||
|  | @ -201,19 +206,20 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R | |||
|         holdOffBuildUntilSave = holdOffBuildUntilUserSave; | ||||
|     } | ||||
| 
 | ||||
|     @Override public void onCreatedFromScratch() { | ||||
|         super.onCreatedFromScratch(); | ||||
|         runIdMigrator = new RunIdMigrator(); | ||||
|         runIdMigrator.created(getBuildDir()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onLoad(ItemGroup<? extends Item> parent, String name) | ||||
|             throws IOException { | ||||
|         super.onLoad(parent, name); | ||||
| 
 | ||||
|         // see https://github.com/jenkinsci/jenkins/pull/10456#issuecomment-2748112449 | ||||
|         // This code can be deleted after several Jenkins releases, | ||||
|         // when it is likely that everyone is running a version equal or higher to this version. | ||||
|         var buildDirPath = getBuildDir().toPath(); | ||||
|         if (Files.deleteIfExists(buildDirPath.resolve("legacyIds"))) { | ||||
|             LOGGER.info("Deleting legacyIds file in " + buildDirPath + ". See https://issues.jenkins" | ||||
|                         + ".io/browse/JENKINS-75465 for more information."); | ||||
|         } | ||||
|         File buildDir = getBuildDir(); | ||||
|         runIdMigrator = new RunIdMigrator(); | ||||
|         runIdMigrator.migrate(buildDir, Jenkins.get().getRootDir()); | ||||
| 
 | ||||
|         TextFile f = getNextBuildNumberFile(); | ||||
|         if (f.exists()) { | ||||
|  |  | |||
|  | @ -45,8 +45,10 @@ import java.util.Objects; | |||
| import java.util.SortedMap; | ||||
| import java.util.logging.Level; | ||||
| import java.util.logging.Logger; | ||||
| import jenkins.model.RunIdMigrator; | ||||
| import jenkins.model.lazy.AbstractLazyLoadRunMap; | ||||
| import jenkins.model.lazy.BuildReference; | ||||
| import jenkins.model.lazy.LazyBuildMixIn; | ||||
| import org.kohsuke.accmod.Restricted; | ||||
| import org.kohsuke.accmod.restrictions.NoExternalUse; | ||||
| 
 | ||||
|  | @ -70,6 +72,10 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R> | |||
| 
 | ||||
|     private Constructor<R> cons; | ||||
| 
 | ||||
|     /** Normally overwritten by {@link LazyBuildMixIn#onLoad} or {@link LazyBuildMixIn#onCreatedFromScratch}, in turn created during {@link Job#onLoad}. */ | ||||
|     @Restricted(NoExternalUse.class) | ||||
|     public RunIdMigrator runIdMigrator = new RunIdMigrator(); | ||||
| 
 | ||||
|     // TODO: before first complete build | ||||
|     // patch up next/previous build link | ||||
| 
 | ||||
|  | @ -150,6 +156,7 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R> | |||
|     @Override | ||||
|     public boolean removeValue(R run) { | ||||
|         run.dropLinks(); | ||||
|         runIdMigrator.delete(dir, run.getId()); | ||||
|         return super.removeValue(run); | ||||
|     } | ||||
| 
 | ||||
|  | @ -220,13 +227,14 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R> | |||
|         return super._put(r); | ||||
|     } | ||||
| 
 | ||||
|     @CheckForNull | ||||
|     @Override public R getById(String id) { | ||||
|         int n; | ||||
|         try { | ||||
|             return getByNumber(Integer.parseInt(id)); | ||||
|         } catch (NumberFormatException e) { // see https://issues.jenkins.io/browse/JENKINS-75476 | ||||
|             return null; | ||||
|             n = Integer.parseInt(id); | ||||
|         } catch (NumberFormatException x) { | ||||
|             n = runIdMigrator.findNumber(id); | ||||
|         } | ||||
|         return getByNumber(n); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  |  | |||
|  | @ -0,0 +1,271 @@ | |||
| /* | ||||
|  * The MIT License | ||||
|  * | ||||
|  * Copyright 2014 Jesse Glick. | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in | ||||
|  * all copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  * THE SOFTWARE. | ||||
|  */ | ||||
| 
 | ||||
| package jenkins.model; | ||||
| 
 | ||||
| import static java.util.logging.Level.FINE; | ||||
| import static java.util.logging.Level.FINER; | ||||
| import static java.util.logging.Level.INFO; | ||||
| import static java.util.logging.Level.WARNING; | ||||
| 
 | ||||
| import edu.umd.cs.findbugs.annotations.CheckForNull; | ||||
| import edu.umd.cs.findbugs.annotations.NonNull; | ||||
| import hudson.Util; | ||||
| import hudson.model.Job; | ||||
| import hudson.model.Run; | ||||
| import hudson.util.AtomicFileWriter; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.nio.file.Files; | ||||
| import java.text.DateFormat; | ||||
| import java.text.ParseException; | ||||
| import java.text.SimpleDateFormat; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Iterator; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.TreeMap; | ||||
| import java.util.logging.Logger; | ||||
| import java.util.regex.Matcher; | ||||
| import java.util.regex.Pattern; | ||||
| import org.kohsuke.accmod.Restricted; | ||||
| import org.kohsuke.accmod.restrictions.NoExternalUse; | ||||
| 
 | ||||
| /** | ||||
|  * Converts legacy {@code builds} directories to the current format. | ||||
|  * | ||||
|  * There would be one instance associated with each {@link Job}, to retain ID → build# mapping. | ||||
|  * | ||||
|  * The {@link Job#getBuildDir} is passed to every method call (rather than being cached) in case it is moved. | ||||
|  */ | ||||
| @Restricted(NoExternalUse.class) | ||||
| public final class RunIdMigrator { | ||||
| 
 | ||||
|     private final DateFormat legacyIdFormatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss"); | ||||
| 
 | ||||
|     static final Logger LOGGER = Logger.getLogger(RunIdMigrator.class.getName()); | ||||
|     private static final String MAP_FILE = "legacyIds"; | ||||
|     /** avoids wasting a map for new jobs */ | ||||
|     private static final Map<String, Integer> EMPTY = new TreeMap<>(); | ||||
| 
 | ||||
|     private @NonNull Map<String, Integer> idToNumber = EMPTY; | ||||
| 
 | ||||
|     public RunIdMigrator() {} | ||||
| 
 | ||||
|     /** | ||||
|      * @return whether there was a file to load | ||||
|      */ | ||||
|     private boolean load(File dir) { | ||||
|         File f = new File(dir, MAP_FILE); | ||||
|         if (!f.isFile()) { | ||||
|             return false; | ||||
|         } | ||||
|         if (f.length() == 0) { | ||||
|             return true; | ||||
|         } | ||||
|         idToNumber = new TreeMap<>(); | ||||
|         try { | ||||
|             for (String line : Files.readAllLines(Util.fileToPath(f), StandardCharsets.UTF_8)) { | ||||
|                 int i = line.indexOf(' '); | ||||
|                 idToNumber.put(line.substring(0, i), Integer.parseInt(line.substring(i + 1))); | ||||
|             } | ||||
|         } catch (Exception x) { // IOException, IndexOutOfBoundsException, NumberFormatException | ||||
|             LOGGER.log(WARNING, "could not read from " + f, x); | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     private void save(File dir) { | ||||
|         File f = new File(dir, MAP_FILE); | ||||
|         try (AtomicFileWriter w = new AtomicFileWriter(f)) { | ||||
|             try { | ||||
|                 synchronized (this) { | ||||
|                     for (Map.Entry<String, Integer> entry : idToNumber.entrySet()) { | ||||
|                         w.write(entry.getKey() + ' ' + entry.getValue() + '\n'); | ||||
|                     } | ||||
|                 } | ||||
|                 w.commit(); | ||||
|             } finally { | ||||
|                 w.abort(); | ||||
|             } | ||||
|         } catch (IOException x) { | ||||
|             LOGGER.log(WARNING, "could not save changes to " + f, x); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Called when a job is first created. | ||||
|      * Just saves an empty marker indicating that this job needs no migration. | ||||
|      * @param dir as in {@link Job#getBuildDir} | ||||
|      */ | ||||
|     public void created(File dir) { | ||||
|         save(dir); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Perform one-time migration if this has not been done already. | ||||
|      * Where previously there would be a {@code 2014-01-02_03-04-05/build.xml} specifying {@code <number>99</number>} plus a symlink {@code 99 → 2014-01-02_03-04-05}, | ||||
|      * after migration there will be just {@code 99/build.xml} specifying {@code <id>2014-01-02_03-04-05</id>} and {@code <timestamp>…</timestamp>} according to local time zone at time of migration. | ||||
|      * Newly created builds are untouched. | ||||
|      * Does not throw {@link IOException} since we make a best effort to migrate but do not consider it fatal to job loading if we cannot. | ||||
|      * @param dir as in {@link Job#getBuildDir} | ||||
|      * @param jenkinsHome root directory of Jenkins (for logging only) | ||||
|      * @return true if migration was performed | ||||
|      */ | ||||
|     public synchronized boolean migrate(File dir, @CheckForNull File jenkinsHome) { | ||||
|         if (load(dir)) { | ||||
|             LOGGER.log(FINER, "migration already performed for {0}", dir); | ||||
|             return false; | ||||
|         } | ||||
|         if (!dir.isDirectory()) { | ||||
|             LOGGER.log(/* normal during Job.movedTo */FINE, "{0} was unexpectedly missing", dir); | ||||
|             return false; | ||||
|         } | ||||
|         LOGGER.log(INFO, "Migrating build records in {0}. See https://www.jenkins.io/redirect/build-record-migration for more information.", dir); | ||||
|         doMigrate(dir); | ||||
|         save(dir); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     private static final Pattern NUMBER_ELT = Pattern.compile("(?m)^  <number>(\\d+)</number>(\r?\n)"); | ||||
| 
 | ||||
|     private void doMigrate(File dir) { | ||||
|         idToNumber = new TreeMap<>(); | ||||
|         File[] kids = dir.listFiles(); | ||||
|         // Need to process symlinks first so we can rename to them. | ||||
|         List<File> kidsList = new ArrayList<>(Arrays.asList(kids)); | ||||
|         Iterator<File> it = kidsList.iterator(); | ||||
|         while (it.hasNext()) { | ||||
|             File kid = it.next(); | ||||
|             String name = kid.getName(); | ||||
|             try { | ||||
|                 Integer.parseInt(name); | ||||
|             } catch (NumberFormatException x) { | ||||
|                 LOGGER.log(FINE, "ignoring nonnumeric entry {0}", name); | ||||
|                 continue; | ||||
|             } | ||||
|             try { | ||||
|                 if (Util.isSymlink(kid)) { | ||||
|                     LOGGER.log(FINE, "deleting build number symlink {0} → {1}", new Object[] {name, Util.resolveSymlink(kid)}); | ||||
|                 } else if (kid.isDirectory()) { | ||||
|                     LOGGER.log(FINE, "ignoring build directory {0}", name); | ||||
|                     continue; | ||||
|                 } else { | ||||
|                     LOGGER.log(WARNING, "need to delete anomalous file entry {0}", name); | ||||
|                 } | ||||
|                 Util.deleteFile(kid); | ||||
|                 it.remove(); | ||||
|             } catch (Exception x) { | ||||
|                 LOGGER.log(WARNING, "failed to process " + kid, x); | ||||
|             } | ||||
|         } | ||||
|         it = kidsList.iterator(); | ||||
|         while (it.hasNext()) { | ||||
|             File kid = it.next(); | ||||
|             try { | ||||
|                 String name = kid.getName(); | ||||
|                 try { | ||||
|                     Integer.parseInt(name); | ||||
|                     LOGGER.log(FINE, "skipping new build dir {0}", name); | ||||
|                     continue; | ||||
|                 } catch (NumberFormatException x) { | ||||
|                     // OK, next… | ||||
|                 } | ||||
|                 if (!kid.isDirectory()) { | ||||
|                     LOGGER.log(FINE, "skipping non-directory {0}", name); | ||||
|                     continue; | ||||
|                 } | ||||
|                 long timestamp; | ||||
|                 try { | ||||
|                     synchronized (legacyIdFormatter) { | ||||
|                         timestamp = legacyIdFormatter.parse(name).getTime(); | ||||
|                     } | ||||
|                 } catch (ParseException x) { | ||||
|                     LOGGER.log(WARNING, "found unexpected dir {0}", name); | ||||
|                     continue; | ||||
|                 } | ||||
|                 File buildXml = new File(kid, "build.xml"); | ||||
|                 if (!buildXml.isFile()) { | ||||
|                     LOGGER.log(WARNING, "found no build.xml in {0}", name); | ||||
|                     continue; | ||||
|                 } | ||||
|                 String xml = Files.readString(Util.fileToPath(buildXml), StandardCharsets.UTF_8); | ||||
|                 Matcher m = NUMBER_ELT.matcher(xml); | ||||
|                 if (!m.find()) { | ||||
|                     LOGGER.log(WARNING, "could not find <number> in {0}/build.xml", name); | ||||
|                     continue; | ||||
|                 } | ||||
|                 int number = Integer.parseInt(m.group(1)); | ||||
|                 String nl = m.group(2); | ||||
|                 xml = m.replaceFirst("  <id>" + name + "</id>" + nl + "  <timestamp>" + timestamp + "</timestamp>" + nl); | ||||
|                 File newKid = new File(dir, Integer.toString(number)); | ||||
|                 move(kid, newKid); | ||||
|                 Files.writeString(Util.fileToPath(newKid).resolve("build.xml"), xml, StandardCharsets.UTF_8); | ||||
|                 LOGGER.log(FINE, "fully processed {0} → {1}", new Object[] {name, number}); | ||||
|                 idToNumber.put(name, number); | ||||
|             } catch (Exception x) { | ||||
|                 LOGGER.log(WARNING, "failed to process " + kid, x); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Tries to move/rename a file from one path to another. | ||||
|      * Uses {@link java.nio.file.Files#move} when available. | ||||
|      * Does not use {@link java.nio.file.StandardCopyOption#REPLACE_EXISTING} or any other options. | ||||
|      * TODO candidate for moving to {@link Util} | ||||
|      */ | ||||
|     static void move(File src, File dest) throws IOException { | ||||
|         try { | ||||
|             Files.move(src.toPath(), dest.toPath()); | ||||
|         } catch (IOException x) { | ||||
|             throw x; | ||||
|         } catch (RuntimeException x) { | ||||
|             throw new IOException(x); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Look up a historical run by ID. | ||||
|      * @param id a nonnumeric ID which may be a valid {@link Run#getId} | ||||
|      * @return the corresponding {@link Run#number}, or 0 if unknown | ||||
|      */ | ||||
|     public synchronized int findNumber(@NonNull String id) { | ||||
|         Integer number = idToNumber.get(id); | ||||
|         return number != null ? number : 0; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Delete the record of a build. | ||||
|      * @param dir as in {@link Job#getBuildDir} | ||||
|      * @param id a {@link Run#getId} | ||||
|      */ | ||||
|     public synchronized void delete(File dir, String id) { | ||||
|         if (idToNumber.remove(id) != null) { | ||||
|             save(dir); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -49,6 +49,7 @@ import java.util.List; | |||
| import java.util.Objects; | ||||
| import java.util.logging.Level; | ||||
| import java.util.logging.Logger; | ||||
| import jenkins.model.RunIdMigrator; | ||||
| import org.kohsuke.accmod.Restricted; | ||||
| import org.kohsuke.accmod.restrictions.DoNotUse; | ||||
| 
 | ||||
|  | @ -146,6 +147,9 @@ public abstract class LazyBuildMixIn<JobT extends Job<JobT, RunT> & Queue.Task & | |||
|                 return loadBuild(dir); | ||||
|             } | ||||
|         }); | ||||
|         RunIdMigrator runIdMigrator = asJob().runIdMigrator; | ||||
|         assert runIdMigrator != null; | ||||
|         r.runIdMigrator = runIdMigrator; | ||||
|         return r; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,47 @@ | |||
| <!-- | ||||
| The MIT License | ||||
| 
 | ||||
| Copyright (c) 2014, CloudBees, Inc. | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| THE SOFTWARE. | ||||
| --> | ||||
| 
 | ||||
| <?jelly escape-by-default='true'?> | ||||
| <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form" xmlns:i="jelly:fmt"> | ||||
|   <l:layout title="Jenkins"> | ||||
|     <l:header /> | ||||
|     <l:main-panel> | ||||
|       <p> | ||||
|         To reverse the effect of build record migration, run the following command | ||||
|         on the server. See <a href="https://www.jenkins.io/redirect/build-record-migration">documentation</a> | ||||
|         for more details: | ||||
|       </p> | ||||
|       <table style="width:100%"> | ||||
|         <tr> | ||||
|           <td style="line-height:2em; width:80%"> | ||||
|             <input type="text" value="${it.command}" style="width:100%"/> | ||||
|           </td> | ||||
|           <td style="line-height:2em"> | ||||
|             <l:copyButton text="${it.command}"/> | ||||
|           </td> | ||||
|         </tr> | ||||
|       </table> | ||||
|     </l:main-panel> | ||||
|   </l:layout> | ||||
| </j:jelly> | ||||
|  | @ -0,0 +1,24 @@ | |||
| # The MIT License | ||||
| # | ||||
| # Bulgarian translation: Copyright (c) 2016, Alexander Shopov <ash@kambanaria.org> | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to deal | ||||
| # in the Software without restriction, including without limitation the rights | ||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| # copies of the Software, and to permit persons to whom the Software is | ||||
| # furnished to do so, subject to the following conditions: | ||||
| # | ||||
| # The above copyright notice and this permission notice shall be included in | ||||
| # all copies or substantial portions of the Software. | ||||
| # | ||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| # THE SOFTWARE. | ||||
| 
 | ||||
| Copied=\ | ||||
|  Копирано | ||||
|  | @ -0,0 +1,23 @@ | |||
| # The MIT License | ||||
| # | ||||
| # Copyright (c) 2017 Daniel Beck and a number of other of contributors | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to deal | ||||
| # in the Software without restriction, including without limitation the rights | ||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| # copies of the Software, and to permit persons to whom the Software is | ||||
| # furnished to do so, subject to the following conditions: | ||||
| # | ||||
| # The above copyright notice and this permission notice shall be included in | ||||
| # all copies or substantial portions of the Software. | ||||
| # | ||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| # THE SOFTWARE. | ||||
| 
 | ||||
| Copied=Kopiert | ||||
|  | @ -0,0 +1,24 @@ | |||
| # The MIT License | ||||
| # | ||||
| # Italian localization plugin for Jenkins | ||||
| # Copyright © 2020 Alessandro Menti | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to deal | ||||
| # in the Software without restriction, including without limitation the rights | ||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| # copies of the Software, and to permit persons to whom the Software is | ||||
| # furnished to do so, subject to the following conditions: | ||||
| # | ||||
| # The above copyright notice and this permission notice shall be included in | ||||
| # all copies or substantial portions of the Software. | ||||
| # | ||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| # THE SOFTWARE. | ||||
| 
 | ||||
| Copied=Copiato | ||||
|  | @ -0,0 +1,23 @@ | |||
| # The MIT License | ||||
| # | ||||
| # Copyright (c) 2004-, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number | ||||
| # of other of contributors | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to deal | ||||
| # in the Software without restriction, including without limitation the rights | ||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| # copies of the Software, and to permit persons to whom the Software is | ||||
| # furnished to do so, subject to the following conditions: | ||||
| # | ||||
| # The above copyright notice and this permission notice shall be included in | ||||
| # all copies or substantial portions of the Software. | ||||
| # | ||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| # THE SOFTWARE. | ||||
| 
 | ||||
|  | @ -0,0 +1,3 @@ | |||
| # This file is under the MIT License by authors | ||||
| 
 | ||||
| Copied=Ископирано | ||||
|  | @ -0,0 +1,3 @@ | |||
| # This file is under the MIT License by authors | ||||
| 
 | ||||
| Copied=Kopierades | ||||
|  | @ -0,0 +1,23 @@ | |||
| # The MIT License | ||||
| # | ||||
| # Copyright (c) 2004-, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors | ||||
| # | ||||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| # of this software and associated documentation files (the "Software"), to deal | ||||
| # in the Software without restriction, including without limitation the rights | ||||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| # copies of the Software, and to permit persons to whom the Software is | ||||
| # furnished to do so, subject to the following conditions: | ||||
| # | ||||
| # The above copyright notice and this permission notice shall be included in | ||||
| # all copies or substantial portions of the Software. | ||||
| # | ||||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
| # THE SOFTWARE. | ||||
| 
 | ||||
| Copied=已複製 | ||||
|  | @ -112,6 +112,7 @@ | |||
|           <Class name="jenkins.fingerprints.FingerprintStorage"/> | ||||
|           <Class name="jenkins.install.SetupWizard"/> | ||||
|           <Class name="jenkins.model.Jenkins"/> | ||||
|           <Class name="jenkins.model.RunIdMigrator"/> | ||||
|           <Class name="jenkins.mvn.SettingsPathHelper"/> | ||||
|           <Class name="jenkins.security.CustomClassFilter$Contributed"/> | ||||
|           <Class name="jenkins.security.ResourceDomainConfiguration"/> | ||||
|  |  | |||
|  | @ -0,0 +1,319 @@ | |||
| /* | ||||
|  * The MIT License | ||||
|  * | ||||
|  * Copyright 2014 Jesse Glick. | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in | ||||
|  * all copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  * THE SOFTWARE. | ||||
|  */ | ||||
| 
 | ||||
| package jenkins.model; | ||||
| 
 | ||||
| import static org.junit.Assert.assertEquals; | ||||
| import static org.junit.Assert.assertFalse; | ||||
| import static org.junit.Assert.assertThrows; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| import static org.junit.Assume.assumeFalse; | ||||
| 
 | ||||
| import hudson.Functions; | ||||
| import hudson.Util; | ||||
| import hudson.util.StreamTaskListener; | ||||
| import java.io.File; | ||||
| import java.io.IOException; | ||||
| import java.nio.charset.Charset; | ||||
| import java.nio.file.Files; | ||||
| import java.nio.file.Path; | ||||
| import java.util.Map; | ||||
| import java.util.TimeZone; | ||||
| import java.util.TreeMap; | ||||
| import java.util.logging.ConsoleHandler; | ||||
| import java.util.logging.Handler; | ||||
| import java.util.logging.Level; | ||||
| import org.apache.commons.io.FileUtils; | ||||
| import org.junit.AfterClass; | ||||
| import org.junit.Before; | ||||
| import org.junit.BeforeClass; | ||||
| import org.junit.Rule; | ||||
| import org.junit.Test; | ||||
| import org.junit.rules.TemporaryFolder; | ||||
| 
 | ||||
| public class RunIdMigratorTest { | ||||
| 
 | ||||
|     @Rule public TemporaryFolder tmp = new TemporaryFolder(); | ||||
| 
 | ||||
|     private static TimeZone defaultTimezone; | ||||
| 
 | ||||
|     /** Ensures that legacy timestamps are interpreted in a predictable time zone. */ | ||||
|     @BeforeClass public static void timezone() { | ||||
|         defaultTimezone = TimeZone.getDefault(); | ||||
|         TimeZone.setDefault(TimeZone.getTimeZone("America/New_York")); | ||||
|     } | ||||
| 
 | ||||
|     @AfterClass public static void tearDown() { | ||||
|         TimeZone.setDefault(defaultTimezone); | ||||
|     } | ||||
| 
 | ||||
|     // TODO could use LoggerRule only if it were extracted to an independent library | ||||
|     @BeforeClass public static void logging() { | ||||
|         RunIdMigrator.LOGGER.setLevel(Level.ALL); | ||||
|         Handler handler = new ConsoleHandler(); | ||||
|         handler.setLevel(Level.ALL); | ||||
|         RunIdMigrator.LOGGER.addHandler(handler); | ||||
|     } | ||||
| 
 | ||||
|     private RunIdMigrator migrator; | ||||
|     private File dir; | ||||
| 
 | ||||
|     @Before public void init() { | ||||
|         migrator = new RunIdMigrator(); | ||||
|         dir = tmp.getRoot(); | ||||
|     } | ||||
| 
 | ||||
|     @Test public void newJob() throws Exception { | ||||
|         migrator.created(dir); | ||||
|         assertEquals("{legacyIds=''}", summarize()); | ||||
|         assertEquals(0, migrator.findNumber("whatever")); | ||||
|         migrator.delete(dir, "1"); | ||||
|         migrator = new RunIdMigrator(); | ||||
|         assertFalse(migrator.migrate(dir, null)); | ||||
|         assertEquals("{legacyIds=''}", summarize()); | ||||
|     } | ||||
| 
 | ||||
|     @Test public void legacy() throws Exception { | ||||
|         assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); | ||||
|         write( | ||||
|                 "2014-01-02_03-04-05/build.xml", | ||||
|                 "<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <number>99</number>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>"); | ||||
|         link("99", "2014-01-02_03-04-05"); | ||||
|         link("lastFailedBuild", "-1"); | ||||
|         link("lastSuccessfulBuild", "99"); | ||||
|         assertEquals( | ||||
|                 "{2014-01-02_03-04-05={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <number>99</number>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>'}, 99=→2014-01-02_03-04-05, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", | ||||
|                 summarize()); | ||||
|         assertTrue(migrator.migrate(dir, null)); | ||||
|         assertEquals( | ||||
|                 "{99={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <id>2014-01-02_03-04-05</id>\n" | ||||
|                     + "  <timestamp>1388649845000</timestamp>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n" | ||||
|                     + "'}", | ||||
|                 summarize()); | ||||
|         assertEquals(99, migrator.findNumber("2014-01-02_03-04-05")); | ||||
|         migrator = new RunIdMigrator(); | ||||
|         assertFalse(migrator.migrate(dir, null)); | ||||
|         assertEquals(99, migrator.findNumber("2014-01-02_03-04-05")); | ||||
|         migrator.delete(dir, "2014-01-02_03-04-05"); | ||||
|         FileUtils.deleteDirectory(new File(dir, "99")); | ||||
|         new File(dir, "lastSuccessfulBuild").delete(); | ||||
|         assertEquals("{lastFailedBuild=→-1, legacyIds=''}", summarize()); | ||||
|     } | ||||
| 
 | ||||
|     @Test public void reRunMigration() throws Exception { | ||||
|         assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); | ||||
|         write("2014-01-02_03-04-04/build.xml", "<run>\n  <number>98</number>\n</run>"); | ||||
|         link("98", "2014-01-02_03-04-04"); | ||||
|         write( | ||||
|                 "99/build.xml", | ||||
|                 "<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <timestamp>1388649845000</timestamp>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>"); | ||||
|         link("lastFailedBuild", "-1"); | ||||
|         link("lastSuccessfulBuild", "99"); | ||||
|         assertEquals( | ||||
|                 "{2014-01-02_03-04-04={build.xml='<run>\n" | ||||
|                     + "  <number>98</number>\n" | ||||
|                     + "</run>'}, 98=→2014-01-02_03-04-04, 99={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <timestamp>1388649845000</timestamp>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99}", | ||||
|                 summarize()); | ||||
|         assertTrue(migrator.migrate(dir, null)); | ||||
|         assertEquals( | ||||
|                 "{98={build.xml='<run>\n" | ||||
|                     + "  <id>2014-01-02_03-04-04</id>\n" | ||||
|                     + "  <timestamp>1388649844000</timestamp>\n" | ||||
|                     + "</run>'}, 99={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <timestamp>1388649845000</timestamp>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-04 98\n" | ||||
|                     + "'}", | ||||
|                 summarize()); | ||||
|     } | ||||
| 
 | ||||
|     @Test public void reverseImmediately() throws Exception { | ||||
|         assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); | ||||
|         File root = dir; | ||||
|         dir = new File(dir, "jobs/somefolder/jobs/someproject/promotions/OK/builds"); | ||||
|         write( | ||||
|                 "99/build.xml", | ||||
|                 "<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <id>2014-01-02_03-04-05</id>\n" | ||||
|                     + "  <timestamp>1388649845000</timestamp>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>"); | ||||
|         link("lastFailedBuild", "-1"); | ||||
|         link("lastSuccessfulBuild", "99"); | ||||
|         write("legacyIds", "2014-01-02_03-04-05 99\n"); | ||||
|         assertEquals( | ||||
|                 "{99={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <id>2014-01-02_03-04-05</id>\n" | ||||
|                     + "  <timestamp>1388649845000</timestamp>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>'}, lastFailedBuild=→-1, lastSuccessfulBuild=→99, legacyIds='2014-01-02_03-04-05 99\n" | ||||
|                     + "'}", | ||||
|                 summarize()); | ||||
|     } | ||||
| 
 | ||||
|     @Test public void reverseAfterNewBuilds() throws Exception { | ||||
|         assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); | ||||
|         File root = dir; | ||||
|         dir = new File(dir, "jobs/someproject/modules/test$test/builds"); | ||||
|         write( | ||||
|                 "1/build.xml", | ||||
|                 "<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <timestamp>1388649845000</timestamp>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>"); | ||||
|         write("legacyIds", ""); | ||||
|         assertEquals( | ||||
|                 "{1={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <timestamp>1388649845000</timestamp>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>'}, legacyIds=''}", | ||||
|                 summarize()); | ||||
|     } | ||||
| 
 | ||||
|     @Test public void reverseMatrixAfterNewBuilds() throws Exception { | ||||
|         assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); | ||||
|         File root = dir; | ||||
|         dir = new File(dir, "jobs/someproject/Environment=prod/builds"); | ||||
|         write( | ||||
|                 "1/build.xml", | ||||
|                 "<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <timestamp>1388649845000</timestamp>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>"); | ||||
|         write("legacyIds", ""); | ||||
|         assertEquals( | ||||
|                 "{1={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <timestamp>1388649845000</timestamp>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>'}, legacyIds=''}", | ||||
|                 summarize()); | ||||
|     } | ||||
| 
 | ||||
|     @Test public void reverseMavenAfterNewBuilds() throws Exception { | ||||
|         assumeFalse("Symlinks don't work well on Windows", Functions.isWindows()); | ||||
|         File root = dir; | ||||
|         dir = new File(dir, "jobs/someproject/test$test/builds"); | ||||
|         write( | ||||
|                 "1/build.xml", | ||||
|                 "<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <timestamp>1388649845000</timestamp>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>"); | ||||
|         write("legacyIds", ""); | ||||
|         assertEquals( | ||||
|                 "{1={build.xml='<?xml version='1.0' encoding='UTF-8'?>\n" | ||||
|                     + "<run>\n" | ||||
|                     + "  <stuff>ok</stuff>\n" | ||||
|                     + "  <timestamp>1388649845000</timestamp>\n" | ||||
|                     + "  <otherstuff>ok</otherstuff>\n" | ||||
|                     + "</run>'}, legacyIds=''}", | ||||
|                 summarize()); | ||||
|     } | ||||
| 
 | ||||
|     // TODO test sane recovery from various error conditions | ||||
| 
 | ||||
|     private void write(String file, String text) throws Exception { | ||||
|         Path path = new File(dir, file).toPath(); | ||||
|         Files.createDirectories(path.getParent()); | ||||
|         Files.writeString(path, text, Charset.defaultCharset()); | ||||
|     } | ||||
| 
 | ||||
|     private void link(String symlink, String dest) throws Exception { | ||||
|         Util.createSymlink(dir, dest, symlink, new StreamTaskListener(System.out, Charset.defaultCharset())); | ||||
|     } | ||||
| 
 | ||||
|     private String summarize() throws Exception { | ||||
|         return summarize(dir); | ||||
|     } | ||||
| 
 | ||||
|     private static String summarize(File dir) throws Exception { | ||||
|         File[] kids = dir.listFiles(); | ||||
|         Map<String, String> m = new TreeMap<>(); | ||||
|         for (File kid : kids) { | ||||
|             String notation; | ||||
|             String symlink = Util.resolveSymlink(kid); | ||||
|             if (symlink != null) { | ||||
|                 notation = "→" + symlink; | ||||
|             } else if (kid.isFile()) { | ||||
|                 notation = "'" + Files.readString(kid.toPath(), Charset.defaultCharset()) + "'"; | ||||
|             } else if (kid.isDirectory()) { | ||||
|                 notation = summarize(kid); | ||||
|             } else { | ||||
|                 notation = "?"; | ||||
|             } | ||||
|             m.put(kid.getName(), notation); | ||||
|         } | ||||
|         return m.toString(); | ||||
|     } | ||||
| 
 | ||||
|     @Test public void move() throws Exception { | ||||
|         File src = tmp.newFile(); | ||||
|         File dest = new File(tmp.getRoot(), "dest"); | ||||
|         RunIdMigrator.move(src, dest); | ||||
|         File dest2 = tmp.newFile(); | ||||
|         assertThrows(IOException.class, () -> RunIdMigrator.move(dest, dest2)); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -0,0 +1,97 @@ | |||
| /* | ||||
|  * The MIT License | ||||
|  * | ||||
|  * Copyright 2023 CloudBees, Inc. | ||||
|  * | ||||
|  * Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
|  * of this software and associated documentation files (the "Software"), to deal | ||||
|  * in the Software without restriction, including without limitation the rights | ||||
|  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
|  * copies of the Software, and to permit persons to whom the Software is | ||||
|  * furnished to do so, subject to the following conditions: | ||||
|  * | ||||
|  * The above copyright notice and this permission notice shall be included in | ||||
|  * all copies or substantial portions of the Software. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
|  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
|  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
|  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
|  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
|  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||||
|  * THE SOFTWARE. | ||||
|  */ | ||||
| 
 | ||||
| package jenkins.model; | ||||
| 
 | ||||
| import static hudson.cli.CLICommandInvoker.Matcher.succeededSilently; | ||||
| import static org.hamcrest.MatcherAssert.assertThat; | ||||
| import static org.junit.Assert.assertNotNull; | ||||
| import static org.junit.Assert.assertTrue; | ||||
| 
 | ||||
| import hudson.cli.CLICommandInvoker; | ||||
| import hudson.cli.CreateJobCommand; | ||||
| import hudson.model.FreeStyleProject; | ||||
| import hudson.model.Item; | ||||
| import hudson.model.User; | ||||
| import java.io.ByteArrayInputStream; | ||||
| import java.io.File; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import org.htmlunit.HttpMethod; | ||||
| import org.htmlunit.WebRequest; | ||||
| import org.junit.Rule; | ||||
| import org.junit.Test; | ||||
| import org.jvnet.hudson.test.Issue; | ||||
| import org.jvnet.hudson.test.JenkinsRule; | ||||
| import org.jvnet.hudson.test.MockAuthorizationStrategy; | ||||
| 
 | ||||
| public class RunIdMigratorTest { | ||||
| 
 | ||||
|     @Rule | ||||
|     public JenkinsRule j = new JenkinsRule(); | ||||
| 
 | ||||
|     @Test | ||||
|     public void legacyIdsPresent() throws Exception { | ||||
|         FreeStyleProject p = j.createFreeStyleProject(); | ||||
|         File legacyIds = new File(p.getBuildDir(), "legacyIds"); | ||||
|         assertTrue(legacyIds.exists()); | ||||
|     } | ||||
| 
 | ||||
|     @Issue("JENKINS-64356") | ||||
|     @Test | ||||
|     public void legacyIdsPresentViaRestApi() throws Exception { | ||||
|         User user = User.getById("user", true); | ||||
|         j.jenkins.setSecurityRealm(j.createDummySecurityRealm()); | ||||
|         j.jenkins.setAuthorizationStrategy(new MockAuthorizationStrategy() | ||||
|                 .grant(Jenkins.READ, Item.CREATE) | ||||
|                 .everywhere() | ||||
|                 .to(user.getId())); | ||||
|         String jobName = "test" + j.jenkins.getItems().size(); | ||||
|         try (JenkinsRule.WebClient wc = j.createWebClient()) { | ||||
|             wc.login(user.getId()); | ||||
|             WebRequest req = new WebRequest(wc.createCrumbedUrl("createItem?name=" + jobName), HttpMethod.POST); | ||||
|             req.setAdditionalHeader("Content-Type", "application/xml"); | ||||
|             req.setRequestBody("<project/>"); | ||||
|             wc.getPage(req); | ||||
|         } | ||||
|         FreeStyleProject p = j.jenkins.getItemByFullName(jobName, FreeStyleProject.class); | ||||
|         assertNotNull(p); | ||||
|         File legacyIds = new File(p.getBuildDir(), "legacyIds"); | ||||
|         assertTrue(legacyIds.exists()); | ||||
|     } | ||||
| 
 | ||||
|     @Issue("JENKINS-64356") | ||||
|     @Test | ||||
|     public void legacyIdsPresentViaCli() { | ||||
|         String jobName = "test" + j.jenkins.getItems().size(); | ||||
|         CLICommandInvoker invoker = new CLICommandInvoker(j, new CreateJobCommand()); | ||||
|         CLICommandInvoker.Result result = invoker.withStdin( | ||||
|                         new ByteArrayInputStream("<project/>".getBytes(StandardCharsets.UTF_8))) | ||||
|                 .invokeWithArgs(jobName); | ||||
|         assertThat(result, succeededSilently()); | ||||
|         FreeStyleProject p = j.jenkins.getItemByFullName(jobName, FreeStyleProject.class); | ||||
|         assertNotNull(p); | ||||
|         File legacyIds = new File(p.getBuildDir(), "legacyIds"); | ||||
|         assertTrue(legacyIds.exists()); | ||||
|     } | ||||
| } | ||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
		Loading…
	
		Reference in New Issue