# HG changeset patch # User Artem Tikhomirov # Date 1328757317 -3600 # Node ID 9fb990c8a7248ae42577acdcac0dc346cb9bce31 # Parent 86f049e6bcae74db26af9339a21fde01d30401fe Investigate approaches to alter Mercurial configuration files diff -r 86f049e6bcae -r 9fb990c8a724 src/org/tmatesoft/hg/core/HgUpdateConfigCommand.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgUpdateConfigCommand.java Thu Feb 09 04:15:17 2012 +0100 @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2012 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.core; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.tmatesoft.hg.internal.ConfigFile; +import org.tmatesoft.hg.internal.Experimental; +import org.tmatesoft.hg.internal.Internals; +import org.tmatesoft.hg.repo.HgRepository; + +/** + * WORK IN PROGRESS, DO NOT USE + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +@Experimental(reason="Investigating approaches to alter Hg configuration files") +public final class HgUpdateConfigCommand extends HgAbstractCommand { + + private final HgRepository repo; + private final File configFile; + + private Map> toRemove; + private Map> toSet; + + private HgUpdateConfigCommand(HgRepository hgRepo, File configurationFile) { + repo = hgRepo; + configFile = configurationFile; + } + + public static HgUpdateConfigCommand forRepository(HgRepository hgRepo) { + return new HgUpdateConfigCommand(hgRepo, new File(".hg/hgrc")); + } + + public static HgUpdateConfigCommand forUser(HgRepository hgRepo) { + return new HgUpdateConfigCommand(null, Internals.getUserConfigurationFileToWrite()); + } + + public static HgUpdateConfigCommand forInstallation() { + return new HgUpdateConfigCommand(null, Internals.getInstallationConfigurationFileToWrite()); + } + + /** + * Remove a property altogether + * @return this for convenience + */ + public HgUpdateConfigCommand remove(String section, String key) { + if (toRemove == null) { + toRemove = new LinkedHashMap>(); + } + List s = toRemove.get(section); + if (s == null) { + toRemove.put(section, s = new ArrayList(5)); + } + s.add(key); + if (toSet != null && toSet.containsKey(section)) { + toSet.get(section).remove(key); + } + return this; + } + + /** + * Delete single attribute in a multi-valued property + * @return this for convenience + */ + public HgUpdateConfigCommand remove(String section, String key, String value) { + throw new UnsupportedOperationException(); + } + + /** + * Set single-valued properties or update multi-valued with a single value + * @return this for convenience + */ + public HgUpdateConfigCommand put(String section, String key, String value) { + if (toSet == null) { + toSet = new LinkedHashMap>(); + } + Map s = toSet.get(section); + if (s == null) { + toSet.put(section, s = new LinkedHashMap()); + } + s.put(key, value); + return this; + } + + /** + * Multi-valued properties + * @return this for convenience + */ + public HgUpdateConfigCommand add(String section, String key, String value) { + throw new UnsupportedOperationException(); + } + + public void execute() throws HgException { + try { + ConfigFile cfg = new ConfigFile(); + cfg.addLocation(configFile); + if (toRemove != null) { + for (Map.Entry> s : toRemove.entrySet()) { + for (String e : s.getValue()) { + cfg.putString(s.getKey(), e, null); + } + } + } + if (toSet != null) { + for (Map.Entry> s : toSet.entrySet()) { + for (Map.Entry e : s.getValue().entrySet()) { + cfg.putString(s.getKey(), e.getKey(), e.getValue()); + } + } + } + cfg.writeTo(configFile); + } catch (IOException ex) { + throw new HgInvalidFileException("Failed to update configuration file", ex, configFile); + } + } + + + public static void main(String[] args) throws Exception { + HgUpdateConfigCommand cmd = HgUpdateConfigCommand.forUser(null); + cmd.remove("test1", "sample1"); + cmd.put("test2", "sample2", "value2"); + cmd.put("ui", "user-name", "Another User "); + cmd.execute(); + } +} diff -r 86f049e6bcae -r 9fb990c8a724 src/org/tmatesoft/hg/internal/ConfigFile.java --- a/src/org/tmatesoft/hg/internal/ConfigFile.java Thu Feb 02 16:16:19 2012 +0100 +++ b/src/org/tmatesoft/hg/internal/ConfigFile.java Thu Feb 09 04:15:17 2012 +0100 @@ -17,11 +17,18 @@ package org.tmatesoft.hg.internal; import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; +import java.io.PrintStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -52,7 +59,7 @@ } public Map getSection(String sectionName) { - if (sections == null) { + if (sections == null) { return Collections.emptyMap(); } int x = sections.indexOf(sectionName); @@ -79,7 +86,31 @@ String value = getSection(sectionName).get(key); return value == null ? defaultValue : value; } - + + public void putString(String sectionName, String key, String newValue) { + Map section = null; + if (sections == null) { + // init, in case we didn't read any file with prev config + sections = new ArrayList(); + content = new ArrayList>(); + } + int x = sections.indexOf(sectionName); + if (x == -1) { + if (newValue == null) { + return; + } + sections.add(sectionName); + content.add(section = new LinkedHashMap()); + } else { + section = content.get(x); + } + if (newValue == null) { + section.remove(key); + } else { + section.put(key, newValue); + } + } + // TODO handle %include and %unset directives // TODO "" and lists // XXX perhaps, single string to keep whole section with substrings for keys/values to minimize number of arrays (String.value) @@ -141,4 +172,42 @@ } assert sections.size() == content.size(); } + + public void writeTo(File f) throws IOException { + byte[] data = compose(); + if (!f.exists()) { + f.createNewFile(); + } + FileChannel fc = new FileOutputStream(f).getChannel(); + FileLock fl = fc.lock(); + try { + fc.write(ByteBuffer.wrap(data)); + } finally { + fl.release(); + fc.close(); + } + } + + private byte[] compose() { + ByteArrayOutputStream bos = new ByteArrayOutputStream(1024); + PrintStream ps = new PrintStream(bos); + Iterator sectionNames = sections.iterator(); + for (Map s : content) { + final String name = sectionNames.next(); // iterate through names despite section may be empty + if (s.isEmpty()) { + continue; // do not write an empty section + } + ps.print('['); + ps.print(name); + ps.println(']'); + for (Map.Entry e : s.entrySet()) { + ps.print(e.getKey()); + ps.print('='); + ps.println(e.getValue()); + } + ps.println(); + } + ps.flush(); + return bos.toByteArray(); + } } diff -r 86f049e6bcae -r 9fb990c8a724 src/org/tmatesoft/hg/internal/Internals.java --- a/src/org/tmatesoft/hg/internal/Internals.java Thu Feb 02 16:16:19 2012 +0100 +++ b/src/org/tmatesoft/hg/internal/Internals.java Thu Feb 09 04:15:17 2012 +0100 @@ -122,6 +122,7 @@ // // XXX perhaps, makes sense to compare getenv(USERPROFILE) and getenv(HOME) and use // them if set (and use both if their values do not match). Only if both not set, rely to user.home? + // Also respect #getUserConfigurationFileToWrite() below configFile.addLocation(new File(System.getProperty("user.home"), "Mercurial.ini")); } else { // FIXME read from install-root @@ -143,4 +144,33 @@ configFile.addLocation(new File(repoRoot, "hgrc")); return configFile; } + + /** + * @return + */ + public static File getInstallationConfigurationFileToWrite() { + // TODO Auto-generated method stub + // FIXME find out install-root + throw new UnsupportedOperationException(); + } + + /** + * @return + */ + public static File getUserConfigurationFileToWrite() { + final File rv = new File(System.getProperty("user.home"), ".hgrc"); + if (rv.exists() && rv.canWrite()) { + return rv; + } + if (runningOnWindows()) { + // try another well-known location + // TODO comment above regarding USERPROFILE and HOME variables applies here as well + File f = new File(System.getProperty("user.home"), "Mercurial.ini"); + if (f.exists() && f.canWrite()) { + return f; + } + } + // fallback to default value + return rv; + } }