|
|
@@ -189,65 +189,135 @@ public class ArchiveCommand extends GitCommand<OutputStream> { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private static class FormatEntry { |
|
|
|
final Format<?> format; |
|
|
|
/** Number of times this format has been registered. */ |
|
|
|
final int refcnt; |
|
|
|
|
|
|
|
public FormatEntry(Format<?> format, int refcnt) { |
|
|
|
if (format == null) |
|
|
|
throw new NullPointerException(); |
|
|
|
this.format = format; |
|
|
|
this.refcnt = refcnt; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/** |
|
|
|
* Available archival formats (corresponding to values for |
|
|
|
* the --format= option) |
|
|
|
*/ |
|
|
|
private static final ConcurrentMap<String, Format<?>> formats = |
|
|
|
new ConcurrentHashMap<String, Format<?>>(); |
|
|
|
private static final ConcurrentMap<String, FormatEntry> formats = |
|
|
|
new ConcurrentHashMap<String, FormatEntry>(); |
|
|
|
|
|
|
|
/** |
|
|
|
* Replaces the entry for a key only if currently mapped to a given |
|
|
|
* value. |
|
|
|
* |
|
|
|
* @param map a map |
|
|
|
* @param key key with which the specified value is associated |
|
|
|
* @param oldValue expected value for the key (null if should be absent). |
|
|
|
* @param newValue value to be associated with the key (null to remove). |
|
|
|
* @return true if the value was replaced |
|
|
|
*/ |
|
|
|
private static <K, V> boolean replace(ConcurrentMap<K, V> map, |
|
|
|
K key, V oldValue, V newValue) { |
|
|
|
if (oldValue == null && newValue == null) // Nothing to do. |
|
|
|
return true; |
|
|
|
|
|
|
|
if (oldValue == null) |
|
|
|
return map.putIfAbsent(key, newValue) == null; |
|
|
|
else if (newValue == null) |
|
|
|
return map.remove(key, oldValue); |
|
|
|
else |
|
|
|
return map.replace(key, oldValue, newValue); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Adds support for an additional archival format. To avoid |
|
|
|
* unnecessary dependencies, ArchiveCommand does not have support |
|
|
|
* for any formats built in; use this function to add them. |
|
|
|
* |
|
|
|
* <p> |
|
|
|
* OSGi plugins providing formats should call this function at |
|
|
|
* bundle activation time. |
|
|
|
* <p> |
|
|
|
* It is okay to register the same archive format with the same |
|
|
|
* name multiple times, but don't forget to unregister it that |
|
|
|
* same number of times, too. |
|
|
|
* <p> |
|
|
|
* Registering multiple formats with different names and the |
|
|
|
* same or overlapping suffixes results in undefined behavior. |
|
|
|
* TODO: check that suffixes don't overlap. |
|
|
|
* |
|
|
|
* @param name name of a format (e.g., "tar" or "zip"). |
|
|
|
* @param fmt archiver for that format |
|
|
|
* @throws JGitInternalException |
|
|
|
* An archival format with that name was already registered. |
|
|
|
* A different archival format with that name was |
|
|
|
* already registered. |
|
|
|
*/ |
|
|
|
public static void registerFormat(String name, Format<?> fmt) { |
|
|
|
// TODO(jrn): Check that suffixes don't overlap. |
|
|
|
|
|
|
|
if (formats.putIfAbsent(name, fmt) != null) |
|
|
|
throw new JGitInternalException(MessageFormat.format( |
|
|
|
JGitText.get().archiveFormatAlreadyRegistered, |
|
|
|
name)); |
|
|
|
if (fmt == null) |
|
|
|
throw new NullPointerException(); |
|
|
|
|
|
|
|
FormatEntry old, entry; |
|
|
|
do { |
|
|
|
old = formats.get(name); |
|
|
|
if (old == null) { |
|
|
|
entry = new FormatEntry(fmt, 1); |
|
|
|
continue; |
|
|
|
} |
|
|
|
if (!old.format.equals(fmt)) |
|
|
|
throw new JGitInternalException(MessageFormat.format( |
|
|
|
JGitText.get().archiveFormatAlreadyRegistered, |
|
|
|
name)); |
|
|
|
entry = new FormatEntry(old.format, old.refcnt + 1); |
|
|
|
} while (!replace(formats, name, old, entry)); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Removes support for an archival format so its Format can be |
|
|
|
* garbage collected. |
|
|
|
* Marks support for an archival format as no longer needed so its |
|
|
|
* Format can be garbage collected if no one else is using it either. |
|
|
|
* <p> |
|
|
|
* In other words, this decrements the reference count for an |
|
|
|
* archival format. If the reference count becomes zero, removes |
|
|
|
* support for that format. |
|
|
|
* |
|
|
|
* @param name name of format (e.g., "tar" or "zip"). |
|
|
|
* @throws JGitInternalException |
|
|
|
* No such archival format was registered. |
|
|
|
*/ |
|
|
|
public static void unregisterFormat(String name) { |
|
|
|
if (formats.remove(name) == null) |
|
|
|
throw new JGitInternalException(MessageFormat.format( |
|
|
|
JGitText.get().archiveFormatAlreadyAbsent, |
|
|
|
name)); |
|
|
|
FormatEntry old, entry; |
|
|
|
do { |
|
|
|
old = formats.get(name); |
|
|
|
if (old == null) |
|
|
|
throw new JGitInternalException(MessageFormat.format( |
|
|
|
JGitText.get().archiveFormatAlreadyAbsent, |
|
|
|
name)); |
|
|
|
if (old.refcnt == 1) { |
|
|
|
entry = null; |
|
|
|
continue; |
|
|
|
} |
|
|
|
entry = new FormatEntry(old.format, old.refcnt - 1); |
|
|
|
} while (!replace(formats, name, old, entry)); |
|
|
|
} |
|
|
|
|
|
|
|
private static Format<?> formatBySuffix(String filenameSuffix) |
|
|
|
throws UnsupportedFormatException { |
|
|
|
if (filenameSuffix != null) |
|
|
|
for (Format<?> fmt : formats.values()) |
|
|
|
for (FormatEntry entry : formats.values()) { |
|
|
|
Format<?> fmt = entry.format; |
|
|
|
for (String sfx : fmt.suffixes()) |
|
|
|
if (filenameSuffix.endsWith(sfx)) |
|
|
|
return fmt; |
|
|
|
} |
|
|
|
return lookupFormat("tar"); //$NON-NLS-1$ |
|
|
|
} |
|
|
|
|
|
|
|
private static Format<?> lookupFormat(String formatName) throws UnsupportedFormatException { |
|
|
|
Format<?> fmt = formats.get(formatName); |
|
|
|
if (fmt == null) |
|
|
|
FormatEntry entry = formats.get(formatName); |
|
|
|
if (entry == null) |
|
|
|
throw new UnsupportedFormatException(formatName); |
|
|
|
return fmt; |
|
|
|
return entry.format; |
|
|
|
} |
|
|
|
|
|
|
|
private OutputStream out; |