hudson.scm.CVSChangeLogSet Maven / Gradle / Ivy
/*
* The MIT License
*
* Copyright (c) 2004-2011, Oracle Corporation, Kohsuke Kawaguchi, Nikita Levyankov, Anton Kozak
*
* 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 hudson.scm;
import hudson.model.AbstractBuild;
import hudson.model.User;
import hudson.scm.CVSChangeLogSet.CVSChangeLog;
import hudson.util.IOException2;
import java.io.IOException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.digester3.Digester;
import org.apache.commons.lang.StringUtils;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
import org.xml.sax.SAXException;
/**
* {@link ChangeLogSet} for CVS.
* @author Kohsuke Kawaguchi
* @author Nikita Levyankov
*/
public final class CVSChangeLogSet extends ChangeLogSet {
private static final Logger LOGGER = Logger.getLogger(CVSChangeLogSet.class.getName());
private List logs;
public CVSChangeLogSet(AbstractBuild,?> build, List logs) {
super(build);
this.logs = Collections.unmodifiableList(logs);
for (CVSChangeLog log : logs)
log.setParent(this);
}
/**
* Returns the read-only list of changes.
*/
public List getLogs() {
return logs;
}
@Override
public String getKind() {
return "cvs";
}
public static CVSChangeLogSet parse( AbstractBuild build, java.io.File f ) throws IOException, SAXException {
Digester digester = new Digester();
ArrayList r = new ArrayList();
digester.push(r);
digester.addObjectCreate("*/entry",CVSChangeLog.class);
digester.addBeanPropertySetter("*/entry/date");
digester.addBeanPropertySetter("*/entry/time");
digester.addBeanPropertySetter("*/entry/author","user");
digester.addBeanPropertySetter("*/entry/msg");
digester.addSetNext("*/entry","add");
digester.addObjectCreate("*/entry/file",File.class);
digester.addBeanPropertySetter("*/entry/file/name");
digester.addBeanPropertySetter("*/entry/file/fullName");
digester.addBeanPropertySetter("*/entry/file/revision");
digester.addBeanPropertySetter("*/entry/file/prevrevision");
digester.addCallMethod("*/entry/file/dead","setDead");
digester.addSetNext("*/entry/file","addFile");
try {
digester.parse(f);
} catch (IOException e) {
throw new IOException2("Failed to parse "+f,e);
} catch (SAXException e) {
throw new IOException2("Failed to parse "+f,e);
}
// merge duplicate entries. Ant task somehow seems to report duplicate entries.
for(int i=r.size()-1; i>=0; i--) {
CVSChangeLog log = r.get(i);
boolean merged = false;
if(!log.isComplete()) {
r.remove(log);
continue;
}
for(int j=0;j files = new ArrayList();
/**
* Returns true if all the fields that are supposed to be non-null is present.
* This is used to make sure the XML file was correct.
*/
public boolean isComplete() {
return date!=null && time!=null && msg!=null;
}
/**
* Checks if two {@link CVSChangeLog} entries can be merged.
* This is to work around the duplicate entry problems.
*/
public boolean canBeMergedWith(CVSChangeLog that) {
if(!this.date.equals(that.date))
return false;
if(!this.time.equals(that.time)) // TODO: perhaps check this loosely?
return false;
if(this.author==null || that.author==null || !this.author.equals(that.author))
return false;
if(!this.msg.equals(that.msg))
return false;
return true;
}
// this is necessary since core and CVS belong to different classloaders.
protected void setParent(ChangeLogSet parent) {
LOGGER.log(Level.FINEST, "Set parent " + parent);
super.setParent(parent);
}
public void merge(CVSChangeLog that) {
this.files.addAll(that.files);
for (File f : that.files)
f.parent = this;
}
@Exported
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
@Exported
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
/**
* This method returns Empty string as revision number.
* CVS stores revision numbers for files in unique way, so it's better to check revision from
* {@link hudson.scm.CVSChangeLogSet.File#getRevision()}
* @return Empty string
*/
@Override
public String getCurrentRevision() {
return StringUtils.EMPTY;
}
@Exported
public User getAuthor() {
if(author==null)
return User.getUnknown();
return author;
}
public Collection getAffectedPaths() {
return new AbstractList() {
public String get(int index) {
return files.get(index).getName();
}
public int size() {
return files.size();
}
};
}
public void setUser(String author) {
this.author = User.get(author);
}
@Exported
public String getUser() {// digester wants read/write property, even though it never reads. Duh.
return author.getDisplayName();
}
@Exported
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void addFile( File f ) {
f.parent = this;
files.add(f);
}
@Exported
public List getFiles() {
return files;
}
@Override
public Collection getAffectedFiles() {
return files;
}
}
@ExportedBean(defaultVisibility=999)
public static class File implements AffectedFile {
private String name;
private String fullName;
private String revision;
private String prevrevision;
private boolean dead;
private CVSChangeLog parent;
/**
* Returns file path with current revision postfix.
*
* For ex. foo/bar/zot.c revision: 1.2.3.4
*/
public String getPath() {
return getName() + " revision: " + getRevision();
}
/**
* Gets the path name in the CVS repository, like
* "foo/bar/zot.c"
*
*
* The path is relative to the workspace root.
*/
@Exported
public String getName() {
return name;
}
/**
* Gets the full path name in the CVS repository,
* like "/module/foo/bar/zot.c"
*
*
* Unlike {@link #getName()}, this method returns
* a full name from the root of the CVS repository.
*/
@Exported
public String getFullName() {
if(fullName==null) {
// Hudson < 1.91 doesn't record full path name for CVS,
// so try to infer that from the current CVS setting.
// This is an approximation since the config could have changed
// since this build has done.
SCM scm = parent.getParent().build.getProject().getScm();
if(scm instanceof CVSSCM) {
CVSSCM cvsscm = (CVSSCM) scm;
if(cvsscm.isFlatten()) {
fullName = '/'+ StringUtils.join(cvsscm.getAllModules(), " ")+'/'+name;
} else {
// multi-module set up.
fullName = '/'+name;
}
} else {
// no way to infer.
fullName = '/'+name;
}
}
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
/**
* Gets just the last component of the path, like "zot.c"
*/
public String getSimpleName() {
int idx = name.lastIndexOf('/');
if(idx>0) return name.substring(idx+1);
return name;
}
public void setName(String name) {
this.name = name;
}
@Exported
public String getRevision() {
return revision;
}
public void setRevision(String revision) {
this.revision = revision;
}
@Exported
public String getPrevrevision() {
return prevrevision;
}
public void setPrevrevision(String prevrevision) {
this.prevrevision = prevrevision;
}
@Exported
public boolean isDead() {
return dead;
}
public void setDead() {
this.dead = true;
}
@Exported
public EditType getEditType() {
// see issue #73. Can't do much better right now
if(dead)
return EditType.DELETE;
if(revision.equals("1.1"))
return EditType.ADD;
return EditType.EDIT;
}
public CVSChangeLog getParent() {
return parent;
}
}
/**
* Represents CVS revision number like "1.5.3.2". Immutable.
*/
public static class Revision {
public final int[] numbers;
public Revision(int[] numbers) {
this.numbers = numbers;
assert numbers.length%2==0;
}
public Revision(String s) {
String[] tokens = s.split("\\.");
numbers = new int[tokens.length];
for( int i=0; i"1.4", "1.5.2.13"->"1.5.2.12", "1.5.2.1"->"1.5"
*
* @return
* null if there's no previous version, meaning this is "1.1"
*/
public Revision getPrevious() {
if(numbers[numbers.length-1]==1) {
// x.y.z.1 => x.y
int[] p = new int[numbers.length-2];
System.arraycopy(numbers,0,p,0,p.length);
if(p.length==0) return null;
return new Revision(p);
}
int[] p = numbers.clone();
p[p.length-1]--;
return new Revision(p);
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
for (int n : numbers) {
if(buf.length()>0) buf.append('.');
buf.append(n);
}
return buf.toString();
}
}
}