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.ModelObjectWithChildren;
 | 
				
			||||||
import jenkins.model.PeepholePermalink;
 | 
					import jenkins.model.PeepholePermalink;
 | 
				
			||||||
import jenkins.model.ProjectNamingStrategy;
 | 
					import jenkins.model.ProjectNamingStrategy;
 | 
				
			||||||
 | 
					import jenkins.model.RunIdMigrator;
 | 
				
			||||||
import jenkins.model.lazy.LazyBuildMixIn;
 | 
					import jenkins.model.lazy.LazyBuildMixIn;
 | 
				
			||||||
import jenkins.scm.RunWithSCM;
 | 
					import jenkins.scm.RunWithSCM;
 | 
				
			||||||
import jenkins.security.HexStringConfidentialKey;
 | 
					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
 | 
					    // this should have been DescribableList but now it's too late
 | 
				
			||||||
    protected CopyOnWriteList<JobProperty<? super JobT>> properties = new CopyOnWriteList<>();
 | 
					    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) {
 | 
					    protected Job(ItemGroup parent, String name) {
 | 
				
			||||||
        super(parent, name);
 | 
					        super(parent, name);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -201,19 +206,20 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
 | 
				
			||||||
        holdOffBuildUntilSave = holdOffBuildUntilUserSave;
 | 
					        holdOffBuildUntilSave = holdOffBuildUntilUserSave;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Override public void onCreatedFromScratch() {
 | 
				
			||||||
 | 
					        super.onCreatedFromScratch();
 | 
				
			||||||
 | 
					        runIdMigrator = new RunIdMigrator();
 | 
				
			||||||
 | 
					        runIdMigrator.created(getBuildDir());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void onLoad(ItemGroup<? extends Item> parent, String name)
 | 
					    public void onLoad(ItemGroup<? extends Item> parent, String name)
 | 
				
			||||||
            throws IOException {
 | 
					            throws IOException {
 | 
				
			||||||
        super.onLoad(parent, name);
 | 
					        super.onLoad(parent, name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // see https://github.com/jenkinsci/jenkins/pull/10456#issuecomment-2748112449
 | 
					        File buildDir = getBuildDir();
 | 
				
			||||||
        // This code can be deleted after several Jenkins releases,
 | 
					        runIdMigrator = new RunIdMigrator();
 | 
				
			||||||
        // when it is likely that everyone is running a version equal or higher to this version.
 | 
					        runIdMigrator.migrate(buildDir, Jenkins.get().getRootDir());
 | 
				
			||||||
        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.");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        TextFile f = getNextBuildNumberFile();
 | 
					        TextFile f = getNextBuildNumberFile();
 | 
				
			||||||
        if (f.exists()) {
 | 
					        if (f.exists()) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,8 +45,10 @@ import java.util.Objects;
 | 
				
			||||||
import java.util.SortedMap;
 | 
					import java.util.SortedMap;
 | 
				
			||||||
import java.util.logging.Level;
 | 
					import java.util.logging.Level;
 | 
				
			||||||
import java.util.logging.Logger;
 | 
					import java.util.logging.Logger;
 | 
				
			||||||
 | 
					import jenkins.model.RunIdMigrator;
 | 
				
			||||||
import jenkins.model.lazy.AbstractLazyLoadRunMap;
 | 
					import jenkins.model.lazy.AbstractLazyLoadRunMap;
 | 
				
			||||||
import jenkins.model.lazy.BuildReference;
 | 
					import jenkins.model.lazy.BuildReference;
 | 
				
			||||||
 | 
					import jenkins.model.lazy.LazyBuildMixIn;
 | 
				
			||||||
import org.kohsuke.accmod.Restricted;
 | 
					import org.kohsuke.accmod.Restricted;
 | 
				
			||||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
 | 
					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;
 | 
					    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
 | 
					    // TODO: before first complete build
 | 
				
			||||||
    // patch up next/previous build link
 | 
					    // patch up next/previous build link
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -150,6 +156,7 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public boolean removeValue(R run) {
 | 
					    public boolean removeValue(R run) {
 | 
				
			||||||
        run.dropLinks();
 | 
					        run.dropLinks();
 | 
				
			||||||
 | 
					        runIdMigrator.delete(dir, run.getId());
 | 
				
			||||||
        return super.removeValue(run);
 | 
					        return super.removeValue(run);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -220,13 +227,14 @@ public final class RunMap<R extends Run<?, R>> extends AbstractLazyLoadRunMap<R>
 | 
				
			||||||
        return super._put(r);
 | 
					        return super._put(r);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @CheckForNull
 | 
					 | 
				
			||||||
    @Override public R getById(String id) {
 | 
					    @Override public R getById(String id) {
 | 
				
			||||||
 | 
					        int n;
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            return getByNumber(Integer.parseInt(id));
 | 
					            n = Integer.parseInt(id);
 | 
				
			||||||
        } catch (NumberFormatException e) { // see https://issues.jenkins.io/browse/JENKINS-75476
 | 
					        } catch (NumberFormatException x) {
 | 
				
			||||||
            return null;
 | 
					            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.Objects;
 | 
				
			||||||
import java.util.logging.Level;
 | 
					import java.util.logging.Level;
 | 
				
			||||||
import java.util.logging.Logger;
 | 
					import java.util.logging.Logger;
 | 
				
			||||||
 | 
					import jenkins.model.RunIdMigrator;
 | 
				
			||||||
import org.kohsuke.accmod.Restricted;
 | 
					import org.kohsuke.accmod.Restricted;
 | 
				
			||||||
import org.kohsuke.accmod.restrictions.DoNotUse;
 | 
					import org.kohsuke.accmod.restrictions.DoNotUse;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -146,6 +147,9 @@ public abstract class LazyBuildMixIn<JobT extends Job<JobT, RunT> & Queue.Task &
 | 
				
			||||||
                return loadBuild(dir);
 | 
					                return loadBuild(dir);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					        RunIdMigrator runIdMigrator = asJob().runIdMigrator;
 | 
				
			||||||
 | 
					        assert runIdMigrator != null;
 | 
				
			||||||
 | 
					        r.runIdMigrator = runIdMigrator;
 | 
				
			||||||
        return r;
 | 
					        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.fingerprints.FingerprintStorage"/>
 | 
				
			||||||
          <Class name="jenkins.install.SetupWizard"/>
 | 
					          <Class name="jenkins.install.SetupWizard"/>
 | 
				
			||||||
          <Class name="jenkins.model.Jenkins"/>
 | 
					          <Class name="jenkins.model.Jenkins"/>
 | 
				
			||||||
 | 
					          <Class name="jenkins.model.RunIdMigrator"/>
 | 
				
			||||||
          <Class name="jenkins.mvn.SettingsPathHelper"/>
 | 
					          <Class name="jenkins.mvn.SettingsPathHelper"/>
 | 
				
			||||||
          <Class name="jenkins.security.CustomClassFilter$Contributed"/>
 | 
					          <Class name="jenkins.security.CustomClassFilter$Contributed"/>
 | 
				
			||||||
          <Class name="jenkins.security.ResourceDomainConfiguration"/>
 | 
					          <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