getCQs() {
+ return cqs;
+ }
+
+ void loadFrom(Config cfg) {
+ projects.clear();
+ cqs.clear();
+
+ for (String id : cfg.getSubsections(S_PROJECT)) {
+ String name = cfg.getString(S_PROJECT, id, K_NAME);
+ Project project = new Project(id, name);
+ project.setComments(cfg.getString(S_PROJECT, id, K_COMMENTS));
+
+ for (String license : cfg.getStringList(S_PROJECT, id, K_LICENSE))
+ project.addLicense(license);
+ projects.add(project);
+ }
+
+ for (String id : cfg.getSubsections(S_CQ)) {
+ CQ cq = new CQ(Long.parseLong(id));
+ cq.setDescription(cfg.getString(S_CQ, id, K_DESCRIPTION));
+ cq.setLicense(cfg.getString(S_CQ, id, K_LICENSE));
+ cq.setUse(cfg.getString(S_CQ, id, K_USE));
+ cq.setState(cfg.getString(S_CQ, id, K_STATE));
+ cq.setComments(cfg.getString(S_CQ, id, K_COMMENTS));
+ cqs.add(cq);
+ }
+ }
+
+ /**
+ * Query the Eclipse Foundation's IPzilla database for CQ records.
+ *
+ * Updates the local {@code .eclipse_iplog} configuration file with current
+ * information by deleting CQs which are no longer relevant, and adding or
+ * updating any CQs which currently exist in the database.
+ *
+ * @param file
+ * local file to update with current CQ records.
+ * @param base
+ * base https:// URL of the IPzilla server.
+ * @param username
+ * username to login to IPzilla as. Must be a Bugzilla username
+ * of someone authorized to query the project's IPzilla records.
+ * @param password
+ * password for {@code username}.
+ * @throws IOException
+ * IPzilla cannot be queried, or the local file cannot be read
+ * from or written to.
+ * @throws ConfigInvalidException
+ * the local file cannot be read, as it is not a valid
+ * configuration file format.
+ */
+ public void syncCQs(File file, URL base, String username, String password)
+ throws IOException, ConfigInvalidException {
+ if (!file.getParentFile().exists())
+ file.getParentFile().mkdirs();
+
+ LockFile lf = new LockFile(file);
+ if (!lf.lock())
+ throw new IOException("Cannot lock " + file);
+ try {
+ FileBasedConfig cfg = new FileBasedConfig(file);
+ cfg.load();
+ loadFrom(cfg);
+
+ IPZillaQuery ipzilla = new IPZillaQuery(base, username, password);
+ Set current = ipzilla.getCQs(projects);
+
+ for (CQ cq : sort(current, CQ.COMPARATOR)) {
+ String id = Long.toString(cq.getID());
+
+ set(cfg, S_CQ, id, K_DESCRIPTION, cq.getDescription());
+ set(cfg, S_CQ, id, K_LICENSE, cq.getLicense());
+ set(cfg, S_CQ, id, K_USE, cq.getUse());
+ set(cfg, S_CQ, id, K_STATE, cq.getState());
+ set(cfg, S_CQ, id, K_COMMENTS, cq.getComments());
+ }
+
+ for (CQ cq : cqs) {
+ if (!current.contains(cq))
+ cfg.unsetSection(S_CQ, Long.toString(cq.getID()));
+ }
+
+ lf.write(Constants.encode(cfg.toText()));
+ if (!lf.commit())
+ throw new IOException("Cannot write " + file);
+ } finally {
+ lf.unlock();
+ }
+ }
+
+ private static void set(Config cfg, String section, String subsection,
+ String key, String value) {
+ if (value == null || "".equals(value))
+ cfg.unset(section, subsection, key);
+ else
+ cfg.setString(section, subsection, key, value);
+ }
+
+ private static > Iterable sort(
+ Collection objs, Q cmp) {
+ ArrayList sorted = new ArrayList(objs);
+ Collections.sort(sorted, cmp);
+ return sorted;
+ }
+}
diff --git a/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/Project.java b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/Project.java
new file mode 100644
index 000000000..6495d38ff
--- /dev/null
+++ b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/Project.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.iplog;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Set;
+import java.util.TreeSet;
+
+/** Description of a project. */
+class Project {
+ /** Sorts projects by unique identities. */
+ static final Comparator COMPARATOR = new Comparator() {
+ public int compare(Project a, Project b) {
+ return a.getID().compareTo(b.getID());
+ }
+ };
+
+ private final String id;
+
+ private final String name;
+
+ private String comments;
+
+ private final Set licenses = new TreeSet();
+
+ private String version;
+
+ /**
+ * @param id
+ * @param name
+ */
+ Project(String id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ /** @return unique identity of this project. */
+ String getID() {
+ return id;
+ }
+
+ /** @return name of this project. */
+ String getName() {
+ return name;
+ }
+
+ /** @return any additional comments about this project. */
+ String getComments() {
+ return comments;
+ }
+
+ void setComments(String comments) {
+ this.comments = comments;
+ }
+
+ /** @return the licenses this project is released under. */
+ Set getLicenses() {
+ return Collections.unmodifiableSet(licenses);
+ }
+
+ void addLicense(String licenseName) {
+ licenses.add(licenseName);
+ }
+
+ String getVersion() {
+ return version;
+ }
+
+ void setVersion(String v) {
+ version = v;
+ }
+
+ @Override
+ public String toString() {
+ return "Project " + getID() + " (" + getName() + ")";
+ }
+}
diff --git a/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/SimpleCookieManager.java b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/SimpleCookieManager.java
new file mode 100644
index 000000000..dca6f1505
--- /dev/null
+++ b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/SimpleCookieManager.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.iplog;
+
+import java.io.IOException;
+import java.net.CookieHandler;
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Dumb implementation of a CookieManager for the JRE.
+ *
+ * Cookies are keyed only by the host name in the URI. Cookie attributes like
+ * domain and path are ignored to simplify the implementation.
+ *
+ * If we are running on Java 6 or later we should favor using the standard
+ * {@code java.net.CookieManager} class instead.
+ */
+public class SimpleCookieManager extends CookieHandler {
+ private Map> byHost = new HashMap>();
+
+ @Override
+ public Map> get(URI uri,
+ Map> requestHeaders) throws IOException {
+ String host = hostOf(uri);
+
+ Map map = byHost.get(host);
+ if (map == null || map.isEmpty())
+ return requestHeaders;
+
+ Map> r = new HashMap>();
+ r.putAll(requestHeaders);
+ StringBuilder buf = new StringBuilder();
+ for (Map.Entry e : map.entrySet()) {
+ if (buf.length() > 0)
+ buf.append("; ");
+ buf.append(e.getKey());
+ buf.append('=');
+ buf.append(e.getValue());
+ }
+ r.put("Cookie", Collections.singletonList(buf.toString()));
+ return Collections.unmodifiableMap(r);
+ }
+
+ @Override
+ public void put(URI uri, Map> responseHeaders)
+ throws IOException {
+ List list = responseHeaders.get("Set-Cookie");
+ if (list == null || list.isEmpty()) {
+ return;
+ }
+
+ String host = hostOf(uri);
+ Map map = byHost.get(host);
+ if (map == null) {
+ map = new HashMap();
+ byHost.put(host, map);
+ }
+
+ for (String hdr : list) {
+ String attributes[] = hdr.split(";");
+ String nameValue = attributes[0].trim();
+ int eq = nameValue.indexOf('=');
+ String name = nameValue.substring(0, eq);
+ String value = nameValue.substring(eq + 1);
+
+ map.put(name, value);
+ }
+ }
+
+ private String hostOf(URI uri) {
+ StringBuilder key = new StringBuilder();
+ key.append(uri.getScheme());
+ key.append(':');
+ key.append(uri.getHost());
+ if (0 < uri.getPort())
+ key.append(':' + uri.getPort());
+ return key.toString();
+ }
+}
diff --git a/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/SingleContribution.java b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/SingleContribution.java
new file mode 100644
index 000000000..2cd5562a1
--- /dev/null
+++ b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/SingleContribution.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package org.eclipse.jgit.iplog;
+
+import java.util.Comparator;
+import java.util.Date;
+
+/** A single contribution by a {@link Contributor}. */
+class SingleContribution {
+ /** Sorts contributors by their name first name, then last name. */
+ public static final Comparator COMPARATOR = new Comparator() {
+ public int compare(SingleContribution a, SingleContribution b) {
+ return a.created.compareTo(b.created);
+ }
+ };
+
+ private final String id;
+
+ private String summary;
+
+ private Date created;
+
+ private String bugId;
+
+ private String size;
+
+ /**
+ * @param id
+ * @param created
+ * @param summary
+ */
+ SingleContribution(String id, Date created, String summary) {
+ this.id = id;
+ this.summary = summary;
+ this.created = created;
+ }
+
+ /** @return unique identity of the contribution. */
+ String getID() {
+ return id;
+ }
+
+ /** @return date the contribution was created. */
+ Date getCreated() {
+ return created;
+ }
+
+ /** @return summary of the contribution. */
+ String getSummary() {
+ return summary;
+ }
+
+ /** @return Bugzilla bug id */
+ String getBugID() {
+ return bugId;
+ }
+
+ void setBugID(String id) {
+ if (id.startsWith("https://bugs.eclipse.org/"))
+ id = id.substring("https://bugs.eclipse.org/".length());
+ bugId = id;
+ }
+
+ String getSize() {
+ return size;
+ }
+
+ void setSize(String sz) {
+ size = sz;
+ }
+}
diff --git a/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/gsql_query.txt b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/gsql_query.txt
new file mode 100644
index 000000000..441cc978d
--- /dev/null
+++ b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/gsql_query.txt
@@ -0,0 +1,22 @@
+# Query for Gerrit Code Review gsql to produce the .git/gerrit_committers
+# file for a project. Needing to do this manually is a horrible hack.
+
+SELECT a.account_id,
+ u.external_id,
+ a.full_name,
+ b.email_address,
+ r.added_on,
+ r.removed_on
+FROM accounts a,
+ account_external_ids b,
+ account_groups g,
+ account_group_members_audit r,
+ account_external_ids u
+WHERE a.account_id = b.account_id
+ AND b.email_address IS NOT NULL
+ AND r.account_id = a.account_id
+ AND r.group_id = g.group_id
+ AND u.account_id = a.account_id
+ AND u.external_id like 'username:%'
+ AND g.name = 'technology.jgit-committers'
+ORDER BY a.full_name, r.added_on;
diff --git a/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/sample_gerrit_committers b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/sample_gerrit_committers
new file mode 100644
index 000000000..0e0b47eb0
--- /dev/null
+++ b/org.eclipse.jgit.iplog/src/org/eclipse/jgit/iplog/sample_gerrit_committers
@@ -0,0 +1,2 @@
+ 1 | username:spearce | Shawn Pearce | sop@google.com | 2009-09-29 16:47:03.0 | NULL
+ 1 | username:spearce | Shawn Pearce | spearce@spearce.org | 2009-09-29 16:47:03.0 | NULL
diff --git a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
index 935212664..d69f14a73 100644
--- a/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.pgm/META-INF/MANIFEST.MF
@@ -18,6 +18,7 @@ Import-Package: org.eclipse.jgit.awtui,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.util,
+ org.eclipse.jgit.iplog,
org.kohsuke.args4j,
org.kohsuke.args4j.spi
Bundle-ActivationPolicy: lazy
diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
index de63adbbc..c88711d0e 100644
--- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
+++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin
@@ -30,3 +30,6 @@ org.eclipse.jgit.pgm.debug.ShowCacheTree
org.eclipse.jgit.pgm.debug.ShowCommands
org.eclipse.jgit.pgm.debug.ShowDirCache
org.eclipse.jgit.pgm.debug.WriteDirCache
+
+org.eclipse.jgit.pgm.eclipse.Iplog
+org.eclipse.jgit.pgm.eclipse.Ipzilla
diff --git a/org.eclipse.jgit.pgm/pom.xml b/org.eclipse.jgit.pgm/pom.xml
index 7b25efe19..69769e872 100644
--- a/org.eclipse.jgit.pgm/pom.xml
+++ b/org.eclipse.jgit.pgm/pom.xml
@@ -1,6 +1,6 @@