@AllArgsConstructor
public class ManagedConfig {

	/**
	 * The configuration folder.
	 */
	public static final String CONFDIR_REL_PATH = "conf";

	/**
	 * The configuration file name.
	 */
	public static final String GITOLITE_CONF_FILE = "gitolite.conf";

	/**
	 * The {@code GitManager} to use.
	 */
	private final GitManager gitManager;

	/**
	 * The {@code KeyStore} to use.
	 */
	private final KeyStore keyStore;

	/**
	 * The {@code Config}.
	 */
	private final Config config;

	/**
	 * Locks used for operations on the {@code Config} and {@code KeyStore}.
	 */
	private final ReadWriteUpdateLock readWriteLock = new ReentrantReadWriteUpdateLock();

	/**
	 * Commit and push changes to the remote.
	 */
	@SneakyThrows
	protected void applyChanges() {
		File confDir = new File(gitManager.getWorkingDirectory(), CONFDIR_REL_PATH);
		File configurationFile = new File(confDir, GITOLITE_CONF_FILE);
		try(BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(configurationFile, false))) {
			config.write(bufferedWriter);
			gitManager.commitChanges();
			gitManager.push();
		}
	}

	/**
	 * Perform read operations to the {@link Config} within a {@code ReadLock}.
	 * @param configInteraction function
	 * @param <T> return type
	 * @return return value
	 */
	public <T> T readConfigWithReturn(Function<? super Config, T> configInteraction) {
		readWriteLock.updateLock().lock();
		try {
			return configInteraction.apply(config);
		}
		finally {
			readWriteLock.updateLock().unlock();
		}
	}

	/**
	 * Perform read operations to the {@link Config} within a {@code ReadLock}.
	 * @param configInteraction function
	 */
	public void readConfig(Consumer<? super Config> configInteraction) {
		readWriteLock.updateLock().lock();
		try {
			configInteraction.accept(config);
		}
		finally {
			readWriteLock.updateLock().unlock();
		}
	}

	/**
	 * Perform write operations to the {@link Config} within a {@code WriteLock}.
	 * Applies the changes to the repository when done.
	 * @param configInteraction function
	 * @see ManagedConfig#applyChanges()
	 */
	public void writeConfig(Consumer<? super Config> configInteraction) {
		readWriteLock.writeLock().lock();
		try {
			configInteraction.accept(config);
			applyChanges();
		} finally {
			readWriteLock.writeLock().unlock();
		}
	}

	/**
	 * Perform write operations to the {@link Config} within a {@code WriteLock}.
	 * Applies the changes to the repository when done.
	 * @param configInteraction function
	 * @param <T> return type
	 * @return return value
	 * @see ManagedConfig#applyChanges()
	 */
	public <T> T writeConfigWithReturn(Function<? super Config, T> configInteraction) {
		readWriteLock.writeLock().lock();
		try {
			T res = configInteraction.apply(config);
			applyChanges();
			return res;
		} finally {
			readWriteLock.writeLock().unlock();
		}
	}

	/**
	 * Perform read operations to the {@link KeyStore} within a {@code ReadLock}.
	 * @param configInteraction function
	 * @param <T> return type
	 * @return return value
	 */
	public <T> T readKeyStore(Function<? super KeyStore, T> configInteraction) {
		readWriteLock.updateLock().lock();
		try {
			return configInteraction.apply(keyStore);
		}
		finally {
			readWriteLock.updateLock().unlock();
		}
	}

	/**
	 * Perform write operations to the {@link KeyStore} within a {@code WriteLock}.
	 * Applies the changes to the repository when done.
	 * @param configInteraction function
	 * @param <T> return type
	 * @return return value
	 * @see ManagedConfig#applyChanges()
	 */
	@SneakyThrows
	public <T> T writeKeyStoreWithReturn(ThrowingFunction<? super KeyStore, T> configInteraction) {
		readWriteLock.writeLock().lock();
		try {
			T res = configInteraction.apply(keyStore);
			applyChanges();
			return res;
		} finally {
			readWriteLock.writeLock().unlock();
		}
	}

	public interface ThrowingConsumer<T> {
		void accept(T value) throws IOException, InterruptedException;
	}

	public interface ThrowingFunction<T, R> {
		R apply(T value) throws IOException, InterruptedException;
	}

	/**
	 * Perform write operations to the {@link KeyStore} within a {@code WriteLock}.
	 * Applies the changes to the repository when done.
	 * @param configInteraction function
	 * @see ManagedConfig#applyChanges()
	 */
	@SneakyThrows
	public void writeKeyStore(ThrowingConsumer<? super KeyStore> configInteraction) {
		readWriteLock.writeLock().lock();
		try {
			configInteraction.accept(keyStore);
			applyChanges();
		} finally {
			readWriteLock.writeLock().unlock();
		}
	}

} @Data
@Accessors(fluent = true)
public class ManagedConfigFactory {

	/**
	 * The key folder.
	 */
	public static final String KEYDIR_REL_PATH = "keydir";

	/**
	 * The configuration folder.
	 */
	public static final String CONFDIR_REL_PATH = "conf";

	/**
	 * The configuration file name.
	 */
	public static final String GITOLITE_CONF_FILE = "gitolite.conf";

	/**
	 * The {@link GitManagerFactory} used to create the {@link GitManager}.
	 */
	private GitManagerFactory gitManagerFactory = new NativeGitManagerFactory();

	/**
	 * The {@link KeyStoreImplFactory} to create the {@link KeyStore}.
	 */
	private KeyStoreImplFactory keyStoreImplFactory = new KeyStoreImplFactory();

	/**
	 * Define the repository folder to use. Defaults to a temporary directory.
	 */
	private File repositoryFolder;

	/**
	 * Initialize a {@link ManagedConfig}.
	 * @param gitoliteAdminRepo Administration repository to clone.
	 * @return The instantiated {@code ManagedConfig}.
	 * @throws IOException If an I/O error occurs.
	 * @throws InterruptedException If the thread was interrupted.
	 */
	public ManagedConfig init(String gitoliteAdminRepo) throws IOException, InterruptedException {
		Preconditions.checkNotNull(gitoliteAdminRepo);
		ensureRepositoryFolderExists();
		GitManager gitManager = getGitManager();
		ensureRepositoryExists(gitManager, gitoliteAdminRepo);

		KeyStore keyStore = getKeyStore();
		Config config = parseConfig();

		return new ManagedConfig(gitManager, keyStore, config);
	}

	/**
	 * Create a temporary folder if null.
	 */
	protected void ensureRepositoryFolderExists() {
		if(repositoryFolder == null) {
			repositoryFolder = Files.createTempDir();
		}
	}

	/**
	 * Function to initialize the {@code GitManager} for the {@code ManagedConfig}.
	 * @return the GitManager implementation
	 */
	protected GitManager getGitManager() {
		return gitManagerFactory.create(repositoryFolder);
	}

	/**
	 * Function to initialize the {@code KeyStore} for the {@code ManagedConfig}.
	 * @return the KeyStore implementation.
	 */
	protected KeyStore getKeyStore() {
		return keyStoreImplFactory.create(new File(repositoryFolder, KEYDIR_REL_PATH));
	}

	/**
	 * Parse the {@code Config} from the configuration file.
	 * @return the parsed config.
	 * @throws IOException If an I/O error occurs.
	 */
	protected Config parseConfig() throws IOException {
		File confDir = new File(repositoryFolder, CONFDIR_REL_PATH);
		File configurationFile = new File(confDir, GITOLITE_CONF_FILE);
		return TokenizerBasedParser.parse(configurationFile);
	}

	/**
	 * Ensure that the administration repository exists. Pull if exists, else,
	 * clone the administration repository.
	 * @param gitManager initialized {@code GitManager}.
	 * @param gitoliteAdminRepo administration repository URL.
	 * @throws IOException If an I/O error occurs.
	 * @throws InterruptedException If the thread was interrupted.
	 */
	protected void ensureRepositoryExists(GitManager gitManager, String gitoliteAdminRepo) throws IOException, InterruptedException {
		if(gitManager.exists()) {
			gitManager.pull();
		}
		else {
			gitManager.clone(gitoliteAdminRepo);
		}
	}

} public interface Config extends Writable {

	/**
	 * Get a group from the config
	 * @param name the name for the group
	 * @return the group
	 */
	GroupRule getGroup(String name);

	/**
	 * Add a group.
	 * @param groupRule GroupRule for the group.
	 */
	void addGroup(GroupRule groupRule);

	/**
	 * Delete a group from the config.
	 * @param groupRule GroupRule for the group.
	 * @return true if the group was removed.
	 */
	boolean deleteGroup(GroupRule groupRule);

	/**
	 * @return all group rules
	 */
	Collection<? extends GroupRule> getGroupRules();

	/**
	 * Remove an identifier
	 * @param identifier The identifier to use
	 */
	void deleteIdentifierUses(Identifiable identifier);

	/**
	 * Get the repository rules for a set of identifiables.
	 * @param identifiables Set of identifiables
	 * @return a collection of repository rules
	 */
	Collection<? extends RepositoryRule> getRepositoryRule(Identifiable... identifiables);

	/**
	 * Get the repository rule for a set of identifiables.
	 * @param identifiables Set of identifiables.
	 * @return a repository rule.
	 */
	RepositoryRule getFirstRepositoryRule(Identifiable... identifiables);

	/**
	 * Add a RepositoryRule.
	 * @param repositoryRule RepositoryRule to add.
	 */
	void addRepositoryRule(RepositoryRule repositoryRule);

	/**
	 * Delete a repository rule.
	 * @param rule Rule to remove.
	 * @return true if the repository rule was removed.
	 */
	boolean deleteRepositoryRule(RepositoryRule rule);

	/**
	 * @return a list of rules
	 */
	Collection<Rule> getRules();

	void clear();
} public interface GitManager {

	/**
	 * Check if a repository is initialized in the current folder.
	 *
	 * @return true if a repository is initialized.
	 * @throws IOException  If the working directory does not contain a readable
	 *            git repository.
	 */
	boolean exists() throws IOException;

	/**
	 * This method attempts to open an existing git repository in the working
	 * directory.
	 *
	 * @throws IOException If the working directory does not contain a readable
	 *            git repository.
	 */
	void open() throws IOException;

	/**
	 * Remove a file from the repository in the working directory.
	 *
	 * @param filePattern The pattern matching all the files which need to be
	 *           removed.
	 *
	 * @throws IOException If no matching file could be found, or the file could
	 *            not be removed.
	 *
	 * @throws GitException If an exception occurred while using the Git API.
	 * @throws InterruptedException If the thread was interrupted.
	 */
	void remove(String filePattern) throws IOException, GitException, InterruptedException;

	/**
	 * This method clones a git repository from the specified URI, to the current
	 * working directory.
	 *
	 * @param uri The URI to clone the git repository from. This cannot be NULL.
	 *
	 * @throws IOException If an I/O error occurs.
	 *
	 * @throws GitException If an exception occurred while using the Git API.
	 * @throws InterruptedException If the thread was interrupted.
	 */
	void clone(String uri) throws IOException, InterruptedException, GitException;

	/**
	 * This method initializes a new git repository in the working directory.
	 *
	 * @throws IOException If an I/O error occurs.
	 * @throws GitException If an exception occurred while using the Git API.
	 * @throws InterruptedException If the thread was interrupted.
	 */
	void init() throws IOException, InterruptedException, GitException;

	/**
	 * This method pulls new commits from the remote git repository.
	 *
	 * @return True if new commits were found and pulled, false otherwise.
	 *
	 * @throws IOException If an I/O error occurs.
	 * @throws InterruptedException If the thread was interrupted.
	 * @throws GitException If an exception occurred while using the Git API.
	 */
	boolean pull() throws IOException, InterruptedException, GitException;

	/**
	 * Commits all changes to the working directory to the local git repository.
	 *
	 * @throws InterruptedException If the thread was interrupted.
	 * @throws IOException If the add or commit operations failed.
	 * @throws GitException If an exception occurred while using the Git API.
	 */
	void commitChanges() throws InterruptedException, IOException, GitException;

	/**
	 * This method pushes the locally committed changes to the remote git
	 * repository.
	 *
	 * @throws IOException If the add or commit operations failed.
	 * @throws InterruptedException If the thread was interrupted.
	 * @throws GitException If an exception occurred while using the Git API.
	 */
	void push() throws IOException, InterruptedException, GitException;

	/**
	 * @return The working directory of this {@link GitManager} object.
	 */
	File getWorkingDirectory();

} public interface GitManagerFactory {

	/**
	 * Instantiate a {@code GitManager} in the given folder.
	 * @param repositoryFolder Root folder for the repository.
	 * @return The instantiated GitManager.
	 */
	GitManager create(File repositoryFolder);

} @Value
public class Identifier implements Identifiable {

	/**
	 * The name for the identifier
	 */
	private final String pattern;

	private final static WeakHashMap<String, WeakReference<Identifier>> identifiableMap = new WeakHashMap<>();

	/**
	 * Get an identifier. As {@code Identifiers} should be immutable, they are stored in a
	 * {@code WeakHashMap} for caching purposes. This method tries to find {@code Identifiers}
	 * in the cache first, and only creates a new instance if not. Therefore, this method is
	 * preferred over using a constructor.
	 *
	 * @param name Name for the identifier.
	 * @return Identifier
	 */
	public static Identifier valueOf(String name) {
		WeakReference<Identifier> ref = identifiableMap.get(name);
		Identifier identifiable;
		if(ref == null || (identifiable = ref.get()) == null) {
			identifiable = new Identifier(name);
			identifiableMap.put(name, new WeakReference<>(identifiable));
		}
		return identifiable;
	}

} @EqualsAndHashCode
public class AccessRule implements Writable {

	private final static String REFS_HEADS = "refs/heads/";

	/**
	 * If no refex is supplied, it defaults to refs/.*, for example in a rule like this:
	 *
	 * {@code <pre>
	 *    RW              =   alice
	 * </pre>}
	 */
	public final static String DEFAULT_REFEX = "refs/.*";

	/**
	 * A pattern for usernames.
	 */
	public final static String USER_PATTERN = "^\\w[\\w._\\@+-]+$";

	/**
	 * The permission field gives the type of access this rule line permits.
	 */
	@Getter
	private final Permission permission;

	/**
	 * A refex is a word I made up to mean "a regex that matches a ref".
	 *
	 * <ul>
	 *    <li>
	 *       If no refex is supplied, it defaults to {@code refs/.*}.
	 *       A refex not starting with {@code refs/} (or {@code VREF/}) is assumed to start with {@code refs/heads/}.
	 *    </li>
	 *    <li>
	 *       A refex is implicitly anchored at the start, but not at the end.
	 *    </li>
	 *    <li>
	 *       In regular expression lingo, a {@code ^} is assumed at the start
	 *       (but no {@code $} at the end is assumed).
	 *    </li>
	 * </ul>
	 */
	@Getter
	private final String refex;

	/**
	 * Like the repos on the repo line, you can have any number of user names and/or user group names on the rule line.
	 * (However, please note that there is no concept of regular expressions for user names).
	 */
	@Getter
	private final InlineUserGroup members;

	/**
	 * Shorthand that allows you to write constructs like: {@code new RepositoryRule(Permission.RW_PLUS, foo)}.
	 * @param permission {@link Permission} for this {@code Rule}.
	 * @param identifiable {@link Identifiable Identifiables} for this {@code Rule}.
	 * @see AccessRule#AccessRule(Permission, String, InlineUserGroup)
	 */
	public AccessRule(final Permission permission, final Identifier... identifiable) {
		this(permission, new InlineUserGroup(identifiable));
	}

	/**
	 * Shorthand that allows you to write constructs like: {@code new RepositoryRule(Permission.RW_PLUS, foo)}.
	 * @param permission {@link Permission} for this {@code Rule}.
	 * @param identifiable {@link Identifiable Identifiables} for this {@code Rule}.
	 * @see AccessRule#AccessRule(Permission, String, InlineUserGroup)
	 */
	public AccessRule(final Permission permission, final GroupRule... identifiable) {
		this(permission, new InlineUserGroup(identifiable));
	}

	/**
	 * Shorthand that allows you to write constructs like: {@code new RepositoryRule(Permission.RW_PLUS, foo)}.
	 * @param permission {@link Permission} for this {@code Rule}.
	 * @param groups groups for this rule.
	 * @param members members for this rule.
	 */
	public AccessRule(final Permission permission, final Collection<? extends GroupRule> groups, final Collection<? extends Identifier> members) {
		this(permission, (String) null, new InlineUserGroup(groups, members));
	}

	protected AccessRule(final Permission permission, final InlineUserGroup members) {
		this(permission, (String) null, members);
	}

	/**
	 * Shorthand that allows you to write constructs like: {@code new RepositoryRule(Permission.RW_PLUS, foo)}.
	 * @param permission {@link Permission} for this {@code Rule}.
	 * @param refex refex for the rule
	 * @param identifiable {@link Identifiable Identifiables} for this {@code Rule}.
	 * @see AccessRule#AccessRule(Permission, String, InlineUserGroup)
	 */
	public AccessRule(final Permission permission, String refex, final Identifier... identifiable) {
		this(permission, refex, new InlineUserGroup(identifiable));
	}

	/**
	 * Shorthand that allows you to write constructs like: {@code new RepositoryRule(Permission.RW_PLUS, foo)}.
	 * @param permission {@link Permission} for this {@code Rule}.
	 * @param refex refex for the rule
	 * @param groups groups for this rule.
	 * @param members members for this rule.
	 */
	public AccessRule(final Permission permission, final String refex,  final Collection<? extends GroupRule> groups, final Collection<? extends Identifier> members) {
		this(permission, refex, new InlineUserGroup(groups, members));
	}

	protected AccessRule(final Permission permission, final String refex, final InlineUserGroup members) {
		this.permission = permission;
		this.refex = refex;
		this.members = members;
		invariant();
	}

	protected final void invariant() {
		// There is a lot room for improvement here, but at least we ensure valid usernames for most cases.
		for(Identifier member : members) {
			Preconditions.checkArgument(member.getPattern().matches(USER_PATTERN), "\"%s\" is not a valid user name", member);
		}
	}

	public String getAdjustedRefex() {
		if(Strings.isNullOrEmpty(refex)) {
			return DEFAULT_REFEX;
		}
		else if(isVREF() || refex.startsWith("refs/")) {
			return refex;
		}
		else {
			return REFS_HEADS.concat(refex);
		}
	}

	public boolean isVREF() {
		return refex.startsWith("VREF/");
	}

	@Override
	public void write(final Writer writer) throws IOException {
		String tempRexes = refex == null ? "" : refex;
		writer.write(String.format("    %-7s %-7s = ", permission.valueOf(), tempRexes));
		members.write(writer);
		writer.write('\n');
		writer.flush();
	}

	@Override
	@SneakyThrows
	public String toString() {
		try(StringWriter stringWriter = new StringWriter()) {
			write(stringWriter);
			return stringWriter.toString();
		}
	}

} @Data
@Builder
//@EqualsAndHashCode(doNotUseGetters = true)
public class GroupRule implements RecursiveAndPrototypeStreamingGroup<GroupRule, Identifier>, Rule, Identifiable {

	/**
	 * {@code @all} is a special group name that is often convenient to use if you really mean "all repos" or "all users".
	 */
	public final static GroupRule ALL = new GroupRule("@all") {

		@Override
		public boolean contains(Object value) {
			return true;
		}

		@Override
		public String toString() { return "@all"; }

		@Override
		public void write(Writer writer) {}

	};

	@Getter
	private final String pattern;

	@Getter
	private GroupRule parent;

	@Singular
	private final List<Identifier> members;

	@Singular
	private final List<GroupRule> groups;

	public GroupRule(final String pattern, final Identifier... members) {
		this(pattern, null, Lists.newArrayList(members), Collections.emptyList());
	}

	public GroupRule(final String pattern,
	                 final GroupRule parent,
	                 final Collection<? extends Identifier> members,
	                 final Collection<? extends GroupRule> groups) {
		Preconditions.checkNotNull(pattern);
		Preconditions.checkNotNull(groups);
		Preconditions.checkNotNull(members);
		Preconditions.checkArgument(pattern.matches("^\\@\\w[\\w._\\@+-]+$"), "\"%s\" is not a valid group name", pattern);

		this.pattern = pattern;
		this.parent = parent;
		this.groups = Lists.newArrayList(groups);
		this.members = Lists.newArrayList(members);
	}

	@Override
	public Stream<Identifier> getOwnMembersStream() {
		return members.stream();
	}

	@Override
	public Stream<GroupRule> getOwnGroupsStream() {
		return groups.stream();
	}

	@Override
	public void add(GroupRule group) {
		groups.add(group);
	}

	@Override
	public boolean remove(Object element) {
		return members.remove(element) | groups.remove(element) | (parent != null && parent.remove(element));
	}

	@Override
	public boolean add(Identifier value) {
		return members.add(value);
	}

	@Override
	public boolean addAll(Collection<? extends Identifier> c) {
		return members.addAll(c);
	}

	@Override
	public boolean removeAll(Collection<?> c) {
		return members.removeAll(c) | groups.removeAll(c);
	}

	@Override
	public boolean retainAll(Collection<?> c) {
		return members.retainAll(c) | groups.retainAll(c);
	}

	@Override
	public void clear() {
		members.clear();
		groups.clear();
	}

	@Override
	public boolean removeIf(Predicate<? super Identifier> filter) {
		return members.removeIf(filter);
	}

	@Override
	public void write(Writer writer) throws IOException {
		writer.write(String.format("%-20s=   %s\n", pattern, Joiner.on(' ').join(Stream.concat(getOwnGroupsStream(), getOwnMembersStream())
			.map(Identifiable::getPattern)
			.iterator())));
		writer.flush();
	}


	@Override
	public String toString() {
		return (String.format("%-20s=   %s\n", pattern, Joiner.on(' ').join(Stream.concat(getOwnGroupsStream(), getOwnMembersStream())
			.map(Identifiable::getPattern)
			.iterator())));
	}

} @Value
@Builder
@EqualsAndHashCode
public class RepositoryRule implements Rule {

	/**
	 * Regex pattern for the repositories.
	 * Due to projects like gtk+, the + character is now considered a valid character for an ordinary repo.
	 * Therefore, a regex like foo/.+ does not look like a regex to gitolite. Use foo/..* if you want that.
	 * Also, ..* by itself is not considered a valid repo regex. Try [a-zA-Z0-9].*. CREATOR/..* will also work.
	 */
	@Singular
	private final List<Identifiable> identifiables;

	/**
	 * Rules active for the repositories that match this pattern.
	 */
	@Singular
	private final List<AccessRule> rules;

	/**
	 * Configuration keys.
	 */
	@Singular
	private final List<ConfigKey> configKeys;

	/**
	 * Helper method to quickly initialize rules in the following way:
	 *
	 * {@code
	 * <pre>
	 *    new RepositoryRuleBlock("foo", new RepositoryRule(RW_PLUS, members);
	 * </pre>}
	 *
	 * @param pattern Pattern to use.
	 * @param rules Rules to use.
	 */
	public RepositoryRule(String pattern, AccessRule... rules) {
		this(Collections.singletonList(new Identifier(pattern)), Arrays.asList(rules), Collections.emptyList());
	}

	/**
	 * Constructor for {@code RepositoryRuleBlock}.
	 *
	 * @param patterns Patterns to use.
	 * @param rules Rules to use.
	 * @param configKeys Keys to use.
	 */
	public RepositoryRule(final Collection<? extends Identifiable> patterns,
	                      final Collection<? extends AccessRule> rules,
	                      final Collection<? extends ConfigKey> configKeys) {
		if (patterns == null || patterns.isEmpty()) {
			throw new IllegalArgumentException("Patterns should not be empty or null");
		}

		if (rules == null || configKeys == null || (rules.isEmpty() && configKeys.isEmpty())) {
			throw new IllegalArgumentException("Repository rule should have rules or config keys");
		}

		patterns.stream().map(Identifiable::getPattern)
			.filter(id -> id.isEmpty() || id.endsWith("/"))
			.findAny().ifPresent(id -> {
				throw new IllegalArgumentException("Invalid refex: " + id);
			});

		this.identifiables = Lists.newArrayList(patterns);
		this.rules = Lists.newArrayList(rules);
		this.configKeys = Lists.newArrayList(configKeys);
	}

	public RepositoryRule addRule(AccessRule accessRule) {
		rules.add(accessRule);
		return this;
	}

	public boolean removeIdentifiable(Identifiable o) {
		return getIdentifiables().remove(o);
	}

	@Override
	public void write(Writer writer) throws IOException {
		writer.write("\nrepo");
		for(Identifiable identifiable : identifiables) {
			writer.write(' ');
			writer.write(identifiable.getPattern());
		}
		writer.write('\n');
		for(AccessRule rule : rules) {
			rule.write(writer);
		}
		if(!rules.isEmpty() && !configKeys.isEmpty()) {
			writer.write('\n');
		}
		for(ConfigKey config : configKeys) {
			config.write(writer);
		}
		writer.flush();
	}

	@Override
	@SneakyThrows
	public String toString() {
		try(StringWriter stringWriter = new StringWriter()) {
			write(stringWriter);
			return stringWriter.toString();
		}
	}

} @AllArgsConstructor(access = AccessLevel.PACKAGE)
public enum BasePermission implements Permission {

	/**
	 * Allow pretty much anything -- fast-forward, rewind or remove branches or tags.
	 */
	RW_PLUS("RW+"),

	/**
	 * Allow fast-forward push of a branch, or create new branch/tag.
	 */
	RW("RW"),

	/**
	 * Allow read operations only.
	 */
	R("R"),

	/**
	 * Create repositories.
	 */
	C("C"),

	/**
	 * Deny access.
	 */
	DENY("-");

	private final String field;

	public static BasePermission parse(String input) {
		for(BasePermission basePermission : values()) {
			if(basePermission.field.equals(input)) {
				return basePermission;
			}
		}
		throw new IllegalArgumentException("No permission found for " + input);
	}

	@Override
	public BasePermission getBasePermission() {
		return this;
	}

	@Override
	public Collection<PermissionModifier> getModifiers() {
		return Collections.emptyList();
	}

	@Override
	public String valueOf() {
		return field;
	}

}
import lombok.SneakyThrows;
import nl.tudelft.ewi.gitolite.ManagedConfig;
import nl.tudelft.ewi.gitolite.ManagedConfigFactory;
import nl.tudelft.ewi.gitolite.config.Config;
import nl.tudelft.ewi.gitolite.git.GitManager;
import nl.tudelft.ewi.gitolite.git.GitManagerFactory;
import nl.tudelft.ewi.gitolite.objects.Identifier;
import nl.tudelft.ewi.gitolite.parser.rules.AccessRule;
import nl.tudelft.ewi.gitolite.parser.rules.GroupRule;
import nl.tudelft.ewi.gitolite.parser.rules.RepositoryRule;
import nl.tudelft.ewi.gitolite.permission.BasePermission;
import org.apache.commons.io.FileUtils;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
package manager;




/**
 * @author Jan-Willem Gmelig Meyling
 */
@RunWith(MockitoJUnitRunner.class)
public class TestConfigOne implements GitManagerFactory {

	@Mock private GitManager gitManager;
	@Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
	private ManagedConfigFactory managedConfigFactory;
	private ManagedConfig managedConfig;