/* * Copyright (C) 2007, Dave Watson * Copyright (C) 2007, Robin Rosenberg * Copyright (C) 2006-2008, Shawn O. Pearce and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.lib; import java.io.Serializable; import java.text.SimpleDateFormat; import java.time.Instant; import java.time.ZoneId; import java.util.Date; import java.util.Locale; import java.util.TimeZone; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.time.ProposedTimestamp; /** * A combination of a person identity and time in Git. * * Git combines Name + email + time + time zone to specify who wrote or * committed something. */ public class PersonIdent implements Serializable { private static final long serialVersionUID = 1L; /** * Get timezone object for the given offset. * * @param tzOffset * timezone offset as in {@link #getTimeZoneOffset()}. * @return time zone object for the given offset. * @since 4.1 */ public static TimeZone getTimeZone(int tzOffset) { StringBuilder tzId = new StringBuilder(8); tzId.append("GMT"); //$NON-NLS-1$ appendTimezone(tzId, tzOffset); return TimeZone.getTimeZone(tzId.toString()); } /** * Format a timezone offset. * * @param r * string builder to append to. * @param offset * timezone offset as in {@link #getTimeZoneOffset()}. * @since 4.1 */ public static void appendTimezone(StringBuilder r, int offset) { final char sign; final int offsetHours; final int offsetMins; if (offset < 0) { sign = '-'; offset = -offset; } else { sign = '+'; } offsetHours = offset / 60; offsetMins = offset % 60; r.append(sign); if (offsetHours < 10) { r.append('0'); } r.append(offsetHours); if (offsetMins < 10) { r.append('0'); } r.append(offsetMins); } /** * Sanitize the given string for use in an identity and append to output. *

* Trims whitespace from both ends and special characters {@code \n < >} that * interfere with parsing; appends all other characters to the output. * Analogous to the C git function {@code strbuf_addstr_without_crud}. * * @param r * string builder to append to. * @param str * input string. * @since 4.4 */ public static void appendSanitized(StringBuilder r, String str) { // Trim any whitespace less than \u0020 as in String#trim(). int i = 0; while (i < str.length() && str.charAt(i) <= ' ') { i++; } int end = str.length(); while (end > i && str.charAt(end - 1) <= ' ') { end--; } for (; i < end; i++) { char c = str.charAt(i); switch (c) { case '\n': case '<': case '>': continue; default: r.append(c); break; } } } private final String name; private final String emailAddress; private final long when; private final int tzOffset; /** * Creates new PersonIdent from config info in repository, with current time. * This new PersonIdent gets the info from the default committer as available * from the configuration. * * @param repo a {@link org.eclipse.jgit.lib.Repository} object. */ public PersonIdent(Repository repo) { this(repo.getConfig().get(UserConfig.KEY)); } /** * Copy a {@link org.eclipse.jgit.lib.PersonIdent}. * * @param pi * Original {@link org.eclipse.jgit.lib.PersonIdent} */ public PersonIdent(PersonIdent pi) { this(pi.getName(), pi.getEmailAddress()); } /** * Construct a new {@link org.eclipse.jgit.lib.PersonIdent} with current * time. * * @param aName * a {@link java.lang.String} object. * @param aEmailAddress * a {@link java.lang.String} object. */ public PersonIdent(String aName, String aEmailAddress) { this(aName, aEmailAddress, SystemReader.getInstance().getCurrentTime()); } /** * Construct a new {@link org.eclipse.jgit.lib.PersonIdent} with current * time. * * @param aName * a {@link java.lang.String} object. * @param aEmailAddress * a {@link java.lang.String} object. * @param when * a {@link org.eclipse.jgit.util.time.ProposedTimestamp} object. * @since 4.6 */ public PersonIdent(String aName, String aEmailAddress, ProposedTimestamp when) { this(aName, aEmailAddress, when.millis()); } /** * Copy a PersonIdent, but alter the clone's time stamp * * @param pi * original {@link org.eclipse.jgit.lib.PersonIdent} * @param when * local time * @param tz * time zone */ public PersonIdent(PersonIdent pi, Date when, TimeZone tz) { this(pi.getName(), pi.getEmailAddress(), when, tz); } /** * Copy a {@link org.eclipse.jgit.lib.PersonIdent}, but alter the clone's * time stamp * * @param pi * original {@link org.eclipse.jgit.lib.PersonIdent} * @param aWhen * local time */ public PersonIdent(PersonIdent pi, Date aWhen) { this(pi.getName(), pi.getEmailAddress(), aWhen.getTime(), pi.tzOffset); } /** * Copy a {@link org.eclipse.jgit.lib.PersonIdent}, but alter the clone's * time stamp * * @param pi * original {@link org.eclipse.jgit.lib.PersonIdent} * @param aWhen * local time as Instant * @since 6.1 */ public PersonIdent(PersonIdent pi, Instant aWhen) { this(pi.getName(), pi.getEmailAddress(), aWhen.toEpochMilli(), pi.tzOffset); } /** * Construct a PersonIdent from simple data * * @param aName a {@link java.lang.String} object. * @param aEmailAddress a {@link java.lang.String} object. * @param aWhen * local time stamp * @param aTZ * time zone */ public PersonIdent(final String aName, final String aEmailAddress, final Date aWhen, final TimeZone aTZ) { this(aName, aEmailAddress, aWhen.getTime(), aTZ.getOffset(aWhen .getTime()) / (60 * 1000)); } /** * Construct a PersonIdent from simple data * * @param aName * a {@link java.lang.String} object. * @param aEmailAddress * a {@link java.lang.String} object. * @param aWhen * local time stamp * @param zoneId * time zone id * @since 6.1 */ public PersonIdent(final String aName, String aEmailAddress, Instant aWhen, ZoneId zoneId) { this(aName, aEmailAddress, aWhen.toEpochMilli(), TimeZone.getTimeZone(zoneId) .getOffset(aWhen .toEpochMilli()) / (60 * 1000)); } /** * Copy a PersonIdent, but alter the clone's time stamp * * @param pi * original {@link org.eclipse.jgit.lib.PersonIdent} * @param aWhen * local time stamp * @param aTZ * time zone */ public PersonIdent(PersonIdent pi, long aWhen, int aTZ) { this(pi.getName(), pi.getEmailAddress(), aWhen, aTZ); } private PersonIdent(final String aName, final String aEmailAddress, long when) { this(aName, aEmailAddress, when, SystemReader.getInstance() .getTimezone(when)); } private PersonIdent(UserConfig config) { this(config.getCommitterName(), config.getCommitterEmail()); } /** * Construct a {@link org.eclipse.jgit.lib.PersonIdent}. *

* Whitespace in the name and email is preserved for the lifetime of this * object, but are trimmed by {@link #toExternalString()}. This means that * parsing the result of {@link #toExternalString()} may not return an * equivalent instance. * * @param aName * a {@link java.lang.String} object. * @param aEmailAddress * a {@link java.lang.String} object. * @param aWhen * local time stamp * @param aTZ * time zone */ public PersonIdent(final String aName, final String aEmailAddress, final long aWhen, final int aTZ) { if (aName == null) throw new IllegalArgumentException( JGitText.get().personIdentNameNonNull); if (aEmailAddress == null) throw new IllegalArgumentException( JGitText.get().personIdentEmailNonNull); name = aName; emailAddress = aEmailAddress; when = aWhen; tzOffset = aTZ; } /** * Get name of person * * @return Name of person */ public String getName() { return name; } /** * Get email address of person * * @return email address of person */ public String getEmailAddress() { return emailAddress; } /** * Get timestamp * * @return timestamp */ public Date getWhen() { return new Date(when); } /** * Get when attribute as instant * * @return timestamp * @since 6.1 */ public Instant getWhenAsInstant() { return Instant.ofEpochMilli(when); } /** * Get this person's declared time zone * * @return this person's declared time zone; null if time zone is unknown. */ public TimeZone getTimeZone() { return getTimeZone(tzOffset); } /** * Get the time zone id * * @return the time zone id * @since 6.1 */ public ZoneId getZoneId() { return getTimeZone().toZoneId(); } /** * Get this person's declared time zone as minutes east of UTC. * * @return this person's declared time zone as minutes east of UTC. If the * timezone is to the west of UTC it is negative. */ public int getTimeZoneOffset() { return tzOffset; } /** * {@inheritDoc} *

* Hashcode is based only on the email address and timestamp. */ @Override public int hashCode() { int hc = getEmailAddress().hashCode(); hc *= 31; hc += (int) (when / 1000L); return hc; } /** {@inheritDoc} */ @Override public boolean equals(Object o) { if (o instanceof PersonIdent) { final PersonIdent p = (PersonIdent) o; return getName().equals(p.getName()) && getEmailAddress().equals(p.getEmailAddress()) && when / 1000L == p.when / 1000L; } return false; } /** * Format for Git storage. * * @return a string in the git author format */ public String toExternalString() { final StringBuilder r = new StringBuilder(); appendSanitized(r, getName()); r.append(" <"); //$NON-NLS-1$ appendSanitized(r, getEmailAddress()); r.append("> "); //$NON-NLS-1$ r.append(when / 1000); r.append(' '); appendTimezone(r, tzOffset); return r.toString(); } /** {@inheritDoc} */ @Override @SuppressWarnings("nls") public String toString() { final StringBuilder r = new StringBuilder(); final SimpleDateFormat dtfmt; dtfmt = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy Z", Locale.US); dtfmt.setTimeZone(getTimeZone()); r.append("PersonIdent["); r.append(getName()); r.append(", "); r.append(getEmailAddress()); r.append(", "); r.append(dtfmt.format(Long.valueOf(when))); r.append("]"); return r.toString(); } }