Hi, I am one of the three selected GSoC(Google summer of Code) students by Git this year. I am a third year computer science student from India. First, I want to thank you all for guiding me through the patch review process and being patient with me. I learned a lot from you all and this helped me polishing my proposal, so I want to thank you all for giving me this opportunity.
My mentors for the project are Matthieu Moy and Ramkumar Ramachandra. My proposal topic is "Git configuration API improvements" and I am appending the whole proposal below ad verbatim for you all to review. I had posted it earlier but it may have gotten lost in the noise. I am looking forward to having a great summer working on git. ##GSoC Proposal : Git configuration API improvements Abstract: ----------- The current method for reading values from the config file (summarized from reading config.c, bulitin/config.c and api-config.txt)in the disk is parsing and reading the file each time a function calls for a config value. git_config() feeds every variable and value to the callback function which decides what to do with them. Due to this approach, it is not uncommon for the configuration to be parsed several times during the run of a Git program, with different callbacks picking out different variables useful to themselves. I propose that instead of reading and parsing the config file multiple times from the disk during a process run, we will construct a in-memory representation of the config file the first time a config value is queried for. Also, the callbacks will be rewired to use the new API functions provided for efficient lookups from the internal cache. After this implementation is validated, new features can be added such as unsetting values from config file. #Proposed Improvements * Fix "git config --unset" to clean up detritus from sections that are left empty and some more aesthetic concerns illustrated below . * Read the configuration from files once and cache the results in an appropriate data structure in memory. * Change git_config() to iterate through the pre-read values in memory rather than re-reading the configuration files. * Add new API calls that allow the cache to be inquired easily and efficiently. * Rewrite callers to use the new API wherever possible. #Possible Problems with this approach, * How to invalidate the cache correctly in the case that the configuration is changed while `git` is executing. * What to do with the comments, which are generally found in config files, and can be places anywhere(above a section header , below a section header etc.)? #Future Improvements * Allow configuration values to be unset via a config file -------------------------------------------------------------------------- ##Changing the git_config api to retrieve values from memory -------------------------------------------------------------------------- #Current Implementation of the git-config subsystem: ---------------------------------------------------- Config files are parsed linearly, and each variable found is passed to a caller-provided callback function. The callback function is responsible for any actions to be taken on the config option, and is free to ignore some options. It is not uncommon for the configuration to be parsed several times during the run of a Git program, with different callbacks picking out different variables useful to themselves. A config callback function takes three parameters: - the name of the parsed variable. This is in canonical "flat" form: the section, subsection, and variable segments will be separated by dots, and the section and variable segments will be all lowercase. E.g., `core.ignorecase`, `diff.SomeType.textconv`. - the value of the found variable, as a string. If the variable had no value specified, the value will be NULL (typically this means it should be interpreted as boolean true). - a void pointer passed in by the caller of the config API; this can contain callback-specific data A config callback should return 0 for success, or -1 if the variable could not be parsed properly. #Current Config Querying: ----------------------- Most code in git,calls `git_config` to look up variables in all config files that Git knows about, using the normal precedence rules. To do this, it calls with a callback function and void data pointer. `git_config` will read all config sources in order of increasing priority. The logic flow is attached as a image[1] . Thus a callback should typically overwrite previously-seenentries with new ones (e.g., if both the user-wide `~/.gitconfig` and repo-specific `.git/config` contains `color.ui`, the config machinery will first feed the user-wide one to the callback, and then the repo-specific one; by overwriting, the higher-priority repo-specific value is left at the end). The current precedence order rates the repo-config file the highest. The `git_config_with_options` function lets the caller examine config while adjusting some of the default behavior of `git_config`. It takes two extra parameters: `filename`:: If this parameter is non-NULL, it specifies the name of a file to parse for configuration, rather than looking in the usual files. Regular `git_config` defaults to `NULL`. `respect_includes`:: Specify whether include directives should be followed in parsed files. Regular `git_config` defaults to `1`. There is a special version of `git_config` called `git_config_early`. This version takes an additional parameter to specify the repository config, instead of having it looked up via `git_path`. This is useful early in a Git program before the repository has been found. I have made a logic diagram for better understanding the query config subsystem, [1]http://imgur.com/CToJXTr #Writing config files to disk: ------------------------------ Git provides two internal functions for setting and unsetting variables in config files, *git_config_set(const char *key, const char *value) *git_config_set_in_file(const char *config_filename,const char *key, const char *value) who call git_config_set_multivar and git_config_set_multivar_in_file respectively. In the end git_config_set_multivar_in_file does all the work. For setting and unsetting values it checks the argument value. If value==NULL, unset in (remove from) config, if value_regex!=NULL, disregard key/value pairs where value does not match. if multi_replace==0, nothing, or only one matching key/value is replaced, else all matching key/values (regardless how many) are removed, before the new pair is written. This function does this: - it locks the config file by creating ".git/config.lock" - it then parses the config using store_aux() as validator to find the position on the key/value pair to replace. If it is to be unset, it must be found exactly once. - the config file is mmap()ed and the part before the match (if any) is written to the lock file, then the changed part and the rest. - the config file is removed and the lock file renamed to it. #New Approach and implementation details: ---------------------------------------- We parse the config file once, storing the raw values to records in memory. After the whole config has been read, iterate through the records, feeding the surviving values into the callback in the order they were originally read (minus deletions). Path to follow for the api conversion, 1. Convert the parser to read into an in-memory representation, but leave git_config() as a wrapper which iterates over it. 2. Add query functions like config_string_get() which will inquire cache for values efficiently. 3. Convert callbacks to query functions one by one. #Choosing a data structure for internal cache: ---------------------------------------------- Currently, the callbacks receives key,value pairs after parsing in a flat form like,"diff.sometype.textconv" and value "true" .These key-value pairs easily translate to a hashmap or a dictionary structure. This data-structure reflects the configuration variables themselves, not the way they appear in the config file. A map (hashtable) associating to each config variable the corresponding value (which maybe a scalar value,boolean or a list, depending on the variable) would be most appropriate in my opinion.They will be saved as "string":"string list" pairs as there are already many helper functions to help converting the string values into their appropriate data type. Advantages of using a Hashmap:- ------------------------------ >>Constant time lookups(O(1)) and deletions.After the initial parsing ,the >>system would use the hashmaps for lookups. >>Read-only accesses happen far more often than updates, so the data structure >>is optimised for lookups. >>Since the key,value pairs are string like for example, >>section.subsection.variable = value ,they are easily hashable and are appropriate for this type of data structure. >>As for the implementation details, there is already a generic hashmap >>implementation built-in .(described in api-hashmap.txt).I could roll out a new one if you want. >>Unsetting previously set value would become as easy as deleting the key-value >>pair ,or setting a invalid flag for that pair during file parsing stage. #Complications in using a hashmap:- ----------------------------------- >>To report errors in the config files, a node remembering where the key-value >>pair came from and from which file, will have to be maintained. It would also be useful in writing key-value pairs back to the file in order of reading. >>In the current code, when somebody does git_config_set() and then later uses >>git_config() to grab the value of the variable set with the first call, we will read the value written to the file with the first call. With the proposed change, if we parse from the file upfront, callers to git_config_set() will need to somehow invalidate that stale copy in memory. We could do two things about it, 1>Easy approach- Discard and rebuilt the cache every time git_config queries for a value. This is currently, what we are doing, reading and parsing the file every time, a value is called for using git_config. 2>Harder approach- Update the changed part. This seems doable, but I will have to experiment with it to see if it works or not as I am not too much familiar with the code base. But take this initial proposal, git_config_set creates a lock file , updates it, deletes the original config file and renames the lockfile to a new config file. We could take this approach and apply it to the in memory-cache and lock it until git_config_set works on it. >>Precedence order of variables read from different sources. Currently the config is read from multiple sources (repo config file `.git/config`,user config file `$HOME/.gitconfig` and system wide config file `/etc/gitconfig`) with the repo config file having the highest preceding order. It is read last in the current parsing order. In my implementation the value of variables will be implemented as list of strings. So it will be a case of "last one wins", where the value with the highest precedence would be inserted last in the list. So a caller asking for a value would just read the last value in the list without worrying about no allocation or ownership complexity. #Changing the parsing code -------------------------- Currently git_parse_source parses the file and feeds each value to the callback function. git_config uses it. To be backward compatible, I would change the git_parse_source to have a switch for the default parsing behaviour and updated parsing parsing behaviour. In the updated one, the file will be read only once to construct the hash map and after that it would iterate over the values querying the cache each time. The callback would be fed these read values only. #Add new API Calls ------------------ A new set of helpers for retrieving values in a non-callback way will be added. Possible implementation of the API, >>extern int get_config_string_multi(const char * variable, int * num_values, >>const char **values) Argument list: key: used for retrieving the value list for the respective key num_values: returns no of values in the list vales: list containing the values as string >> A singleton wrapper would look like char *git_get_config_string(const char >> *name) return the last value for the key "name"(thus implementing the last one wins semantic) Possible implementation (copied directly from the mailing list discussion ) const char *git_get_config_string(const char *name) { const char **values, *result; int num_values; if (git_get_config_string_multi("sample.str", &num_values, &values)) return NULL; result = num_values ? values[num_values - 1] : NULL; free(values); return result; } As there is already a set of helpers for validating the values and converting them to appropriate data types in config.c there won't be any need for specialized functions for each data type. #Rewrite callers: ---------------- After implementing the above ,this would be just busywork and pretty much doable.The callbacks would have to be reworked to use the new api functions. ---------------------------------------------------------------------- ##Tidy configuration files ---------------------------------------------------------------------- #First case: When a configuration file is repeatedly modified, often garbage is left behind. For example, after git config pull.rebase true git config --unset pull.rebase git config pull.rebase true git config --unset pull.rebase the bottom of the configuration file is left with the useless lines [pull] [pull] #Case 2 Also,setting a config value, appends the key-value pair at the end of file without checking for matching empty main section headers. It works fine if the main section header has an already present sub-key. for example:- git config pull.rebase true git config --unset pull.rebase git config pull.rebase true git config pull.option true gives [pull] [pull] rebase = true option = true #Case 3 Also, a possible detriment is presence of comments, For Example:- [my] # This section is for my own private settings Another example you need to consider is # Comment regarding the entire [my] section [my] # Comment on the item foo foo = true # Comment on the item bar bar = false What should "git config --unset my.foo && git config -unset my.bar" do? What if these are done in a different order? #Expected output: 1. When we delete the last key in a section, it automatically delete the section header.(This is debatable) 2. When we add a key into a section, we should be able to reuse the same section header, even if that section did not have any keys in it already(empty section header). #Suggested Approaches :- -------------------------------------- Case first Leave the empty section header as it was and when a new value is set, reuse the header instead of appending at the end of the config file. This can be implemented by changing git_config_set a little, even without using the internal cache. If we use the internal cache,to rewrite the file after setting or unsetting the value, this problem will vanish automatically. Case second This is also a git_config_set problem and can be solved by either using the cache to rewrite the file every time setting or unsetting the value, or reuse the empty section header when adding a new value pair into it. Case three Its a controversial issue , in which the comments added by the user might signify anything(commenting on section header or a particular variable or the above header).I think the comments should be left as it is , as some users leave empty section headers and comments for future use. So removing them may prove detrimental.But , other suggestions are welcome for this problem .The mailing list itself was conflicted about this option. links:- [1]http://thread.gmane.org/gmane.comp.version-control.git/219505 [2]http://thread.gmane.org/gmane.comp.version-control.git/208113 ------------------------------------------------------------------------------------------------------------------ Cheers, Tanay Abhra. -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majord...@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html