<div dir="ltr"><div><div>Hi Iulian,<br><br>this is a long review.<br><br>I add a table of contents so to make random access easier.<br><br>1. "#define _GNU_SOURCE", wat<br>2. log template is better changed<br>3. Rename the 'adding_data()' function<br>
4. Rename "new_cset_size" and "cset" local variable in 'erase_cset()'<br>5. Check correct usage of free(3)<br>6. No reason why hg_log() should return a hg_csetstream_buffer*<br>7. Discuss return value of hg_fetch_cset_entry()<br>
8. Do we really need to expose hg_csetstream_buffer* to the user code<br>9. Review your logic in hg_fetch_cset_entry(), make it more "linear"<br>10. hg_log() should handle error message, too<br>11. strlen(3) is not a null byte detector, don't use it as such<br>
12. hg_exitcode() to be called by user code, not inside hg_fetch_cset_entry()<br><br><br>:::: diff --git a/client.c b/client.c<br>:::: new file mode 100644<br>:::: --- /dev/null<br>:::: +++ b/client.c<br>:::: @@ -0,0 +1,175 @@<br>
:::: +#define _GNU_SOURCE<br><br>I would prefer not to see the _GNU_SOURCE macro identifier defined in your source.<br>Either make your code work without it, or provide a thorough explanation of why<br>it is so desperately needed.<br>
<br>:::: +#include <errno.h><br>:::: +#include <fcntl.h><br>:::: +#include <stdlib.h><br>:::: +#include <stdio.h><br>:::: +#include <string.h><br>:::: +#include <unistd.h><br>:::: +#include <sys/types.h><br>
:::: +#include <sys/wait.h><br>:::: +#include <signal.h><br>:::: +<br>:::: +<br>:::: +#include "client.h"<br>:::: +#include "utils.h"<br>:::: +<br>:::: +#define HGPATH "hg"<br>:::: +#define CHANGESET "\\0{rev}\\n{node}\\n{tags}\\n{branch}\\n{author}\<br>
:::: +                        \\n{date|isodate}\\n{desc}"<br><br>As I write below, I think that hg_fetch_cset_entry() is a little less<br>cumbersome to implement if the '\0' is at the end of the changeset data<br>
instead of being at the beginning.<br><br>Reason:<br>in either case, one changeset of your history has to be "special cased".<br>If the '\0' marker is at the beginning, the last changeset has to be treated<br>
exceptionally (where will it end?).<br>If the '\0' marker is at the end, the first changeset has to be treated<br>exceptionally (where will it begin?).<br>But in the latter case, you know the answer, since the changeset data begins<br>
right after you issue the command 'hg log' to cmdsrv.<br><br>We chosed the above template since it's the same as in JavaHG at commit<br><a href="https://bitbucket.org/aragost/javahg/commits/7855d21c99e1#chg-src/main/java/com/aragost/javahg/Changeset.java">https://bitbucket.org/aragost/javahg/commits/7855d21c99e1#chg-src/main/java/com/aragost/javahg/Changeset.java</a><br>
But I went checking again, and it looks like they ended up with '\0' at the end:<br><a href="https://bitbucket.org/aragost/javahg/src/tip/src/main/resources/styles/changesets.style?at=default">https://bitbucket.org/aragost/javahg/src/tip/src/main/resources/styles/changesets.style?at=default</a><br>
<br>Martin Geisler please correct me if I'm wrong.<br><br>:::: +<br>:::: +<br>:::: +<br>:::: +/**<br>:::: + * \brief 'Parse a changeset'. It's more like pointing to the correct position.<br>:::: + *<br>:::: + * The changeset could be found on buff pointer. To not duplicate the data I<br>
:::: + * choose to point every hg_cset_entry field to the right position.<br>:::: + * \param cset The pointer where changeset could be found.<br>:::: + * \param ce   The hg_cset_entry structure where the changeset will be parse.<br>
:::: + * \retval 0 if successful.<br>:::: + * */<br>:::: +int parse_changeset(char *cset, hg_cset_entry *ce)<br>:::: +{<br>:::: +    char *position = cset;<br>:::: +    /* set pointer for revision position */<br>:::: +    ce->rev = cset;<br>
:::: +    position = strstr(position, "\n");<br>:::: +    cset[position - cset] = '\0';<br>:::: +<br>:::: +    /* set pointer for node position */<br>:::: +    ce->node = position + 1;<br>:::: +    position = strstr(position + 1, "\n");<br>
:::: +    cset[position - cset] = '\0';<br>:::: +<br>:::: +    /* set pointer for tag position */<br>:::: +    ce->tags = position + 1;<br>:::: +    position = strstr(position + 1, "\n");<br>:::: +    cset[position - cset] = '\0';<br>
:::: +<br>:::: +    /* set pointer for branch position */<br>:::: +    ce->branch = position + 1;<br>:::: +    position = strstr(position + 1, "\n");<br>:::: +    cset[position - cset] = '\0';<br>:::: +<br>
:::: +    /* set pointer for author position */<br>:::: +    ce->author = position + 1;<br>:::: +    position = strstr(position + 1, "\n");<br>:::: +    cset[position - cset] = '\0';<br>:::: +<br>:::: +    /* set pointer for data position */<br>
:::: +    ce->date = position + 1;<br>:::: +    position = strstr(position + 1, "\n");<br>:::: +    cset[position - cset] = '\0';<br>:::: +<br>:::: +    /* set pointer for description position */<br>:::: +    ce->desc = position + 1;<br>
:::: +    /* */<br>:::: +    return 0;<br>:::: +}<br>:::: +<br>:::: +/* Adding to the destination pointer the source pointer. */<br>:::: +int adding_data(char **dest, char *source, int dsize, int ssize)<br><br>Rename to 'append_data'. Please don't use the gerund tense in function names,<br>
the simple present tense is better.<br><br>:::: +{<br>:::: +    if(*dest == NULL){<br>:::: +        *dest = malloc(ssize + 1);<br>:::: +        memcpy(*dest, source, ssize + 1);<br>:::: +    } else {<br>:::: +        *dest = realloc(*dest, dsize + ssize + 2);<br>
:::: +        memcpy(*dest + dsize, source, ssize + 1);<br>:::: +    }<br>:::: +    return 0;<br>:::: +}<br>:::: +<br>:::: +/* Erase the top cset from cset pointer. */<br>:::: +int erase_cset(char **cset, int buf_size, int first_cset_size)<br>
:::: +{<br>:::: +    int new_cset_size = buf_size - first_cset_size;<br><br>(*) "new_cset_size" it is *not* the size of a changeset, it's the size of the buffer.<br>    Name it accordingly, like "new_buf_size".<br>
(*) 'cset' is not changeset data, is the content of a raw unparsed buffer.<br>    Name it accordingly.<br><br>:::: +    char *new_cset = malloc(new_cset_size + 1);<br>:::: +    memcpy(new_cset, *cset + first_cset_size, new_cset_size + 1);<br>
:::: +    free(*cset);<br><br>The *cset you're free'ing here was malloc'ed in the 'adding_data' function,<br>as far as I can tell.<br>There you where appending a chunk of the input stream to your internal buffer;<br>
this chunk could possibly contain more than one changeset segment.<br><br>Please convince me that here you're not free'ing data below the "first changeset" boundary,<br>because it looks very much like you are doing so.<br>
<br>:::: +    *cset = new_cset;<br>:::: +    return new_cset_size;<br>:::: +}<br>:::: +<br>:::: +<br>:::: +/* The high level log command for hglib API. */<br>:::: +hg_csetstream_buffer *hg_log(hg_handle *handle, char *option[])<br>
:::: +{<br><br>I really do not understand why hg_log() has to return a hg_csetstream_buffer*.<br>There is no special information necessary to initialize the hg_csetstream_buffer*<br>that is available only from inside hg_log().<br>
It is basically just the hg_handle, which is all over the place.<br><br>And, as I write a few lines below, I think hg_csetstream_buffer* is an internal<br>that should not be exposed to the user code.<br><br>:::: +    hg_csetstream_buffer *cbuf = malloc(sizeof(hg_csetstream_buffer));<br>
:::: +    cbuf->handle = handle;<br>:::: +<br>:::: +    cbuf->command = cmdbuilder("log", option, "--template", CHANGESET,<br>:::: +                            NULL);<br>:::: +<br>:::: +    if(hg_rawcommand(handle, cbuf->command) < 0){<br>
:::: +        return NULL;<br>:::: +    }<br>:::: +<br>:::: +    cbuf->buffer = NULL;<br>:::: +    cbuf->buf_size = 0;<br>:::: +    cbuf->is_send = 0;<br>:::: +<br>:::: +    return cbuf;<br>:::: +}<br>:::: +<br>:::: +/* The cbuf next step. Getting the next changeset. */<br>
:::: +int hg_fetch_cset_entry(hg_csetstream_buffer *cbuf, hg_cset_entry *centry)<br>:::: +{<br><br>Let's look at things from 100 kilometers away:<br>here our goal is to write a function that enables the pattern<br><br>
while( hg_fetch_cset_entry( cset_entry ) )<br>        { /* ...do stuff with cset_entry... */ }<br><br>cset_entry is "something" that contains info about *one*<br>and *only one* changeset.<br>The return value of hg_fetch_cset_entry() should be such that<br>
<br>(*) it's bool eval-ed to 1 if there is stuff to fetch,<br>(*) it's bool eval-ed to 0 if there is nothing more to fetch,<br>(*) and it can give clues if something goes wrong.<br><br>Pretty much like read(2).<br>
<br>Looking at your signature, I see two "output parameters":<br>(1) hg_csetstream_buffer *cbuf<br>(2) hg_cset_entry *centry<br><br>Now, the pointer cbuf look *a lot* like something<br>the user could not care less about. It's an "internal".<br>
<br>I wander if we can get rid of it, i.e. do not expose it on the function prototype.<br>I *do understand* that the only reason we keep cbuf out of hg_fetch_cset_entry()<br>is because we need it to be "persistent" across multiple function calls...<br>
I just don't like it and wander if it can be done better and cleaner.<br><br>If, on the other side, I am wrong and cbuf is something the user code<br>will need at some time (but I doubt it), then hg_fetch_cset_entry()<br>
is producing two different outputs (cbuf and centry), which is a clue<br>that probably it's doing too much.<br><br>:::: +    hg_header head = hg_head(cbuf->handle);<br>:::: +    int exitcode;<br>:::: +    char *get_data;<br>
:::: +    int read_size;<br>:::: +<br>:::: +    /* Erase the first cset from cset pointer.<br>:::: +     * This cset was already pass to user.*/<br>:::: +    if(cbuf->is_send && cbuf->buf_size){<br>:::: +        cbuf->buf_size = erase_cset(&cbuf->buffer, cbuf->buf_size,<br>
:::: +                        cbuf->first_cset_size);<br>:::: +    }<br><br>You start by cleaning up. I don't get it.<br>Can't you do this right after you parse the stream segment that correspond<br>to a full changeset?<br>
I think the field hg_csetstream_buffer.is_send (which should be spelled 'is_sent', btw)<br>is useless and confusing.<br>If you clean up right after you use the data, no need to keep track of what is sent or not.<br>
<br>More generally, I think the whole function needs a rewrite.<br>It's convoluted, difficult to follow, the intent is not clear.<br>To an external reader it looks like everytime you encountered a bug<br>you added an 'if-then' statement here and there.<br>
At a first glance, I could not even say if all conditions are covered<br>(if you put an 'if-then' without an 'else', you're likely to be looking for troubles).<br><br>I suggest you rewrite the body with something like:<br>
<br>if (buffer contains null byte){<br>        consume data;<br>        return something;<br>} else {<br>        read until you get a null byte;<br>}<br><br>(the above works if the null byte '\0' is at the end of the changeset template.<br>
<br>:::: +    while(head.channel != 'r'){<br><br>I already told you this, just a reminder: you're forgetting the error channel 'e' here.<br>Use the callback strategy to handle error messages.<br><br>:::: +        /* If there is a cset in cset pointer, then parse it and send<br>
:::: +         * it to user.*/<br>:::: +        if(cbuf->buffer && strlen(cbuf->buffer + 1) < cbuf->buf_size -1){<br><br>I *absolutely* don't like the use of strlen as a "null-byte" detector.<br>
As you can imagine, strlen keeps a local pointer that runs until it finds '\0':<br><a href="https://sourceware.org/git/?p=glibc.git;a=blob;f=string/strlen.c">https://sourceware.org/git/?p=glibc.git;a=blob;f=string/strlen.c</a><br>
Since there is no guarantee that in your case the '\0' will ever be found,<br>this pointer can go a long way before strlen will return a value.<br><br>Please look for '\0' explicitely, like<br><br>-- -- >8 -- -- cut here -- -- >8 -- -- >8<br>
char *char_ptr;<br>int null_byte_found = 0;<br>size_t cset_size = 0;<br><br>for (char_ptr = cbuf->buffer; char_ptr < cbuf->buffer + cbuf->buf_size; ++char_ptr)<br>        if (*char_ptr == '\0')<br>                int null_byte_found = 1;<br>
                break;<br><br>if (null_byte_found)<br>        cset_size = char_ptr - cbuf->buffer;<br>-- -- >8 -- -- cut here -- -- >8 -- -- >8<br><br>:::: +            cbuf->first_cset_size = strlen(cbuf->buffer + 1) + 1;<br>
:::: +            parse_changeset(cbuf->buffer + 1, centry);<br>:::: +            cbuf->is_send = 1;<br>:::: +            return head.length;<br><br>Here you return head.length, which makes very little sense:<br>it's just the lenght of the last 'o' chunk you got from cmdsrv,<br>
while the parsed changeset you're returning (written in *centry)<br>might have been built up by multiple 'o' chunks one sticked after the other.<br><br>Another thing concerns me (we've discussed it over IRC, just reporting here<br>
for posterity): in your current hg_rawread implementation, if cmdsrv sends<br>you a chunk bigger than the size you're willing to read (i.e. you read in 4K chunks),<br>what you do is *writing* the amount of bytes still to be read into hg_header.length<br>
(see <a href="http://thread.gmane.org/gmane.comp.version-control.mercurial.devel/62348/focus=62351">http://thread.gmane.org/gmane.comp.version-control.mercurial.devel/62348/focus=62351</a> )<br><br>So: either we find a way to "const-ize" all variables of type hg_header,<br>
either we don't refer to those values out of the blue when various side-effects<br>are playing jokes under the hood.<br><br>:::: +        }<br>:::: +        else{<br>:::: +            /* Getting the next data from cmdserver and put on the<br>
:::: +             * end of the cset pointer. */<br>:::: +            get_data = malloc(head.length + 1);<br>:::: +            if(read_size = hg_rawread(cbuf->handle, get_data,<br>:::: +                        head.length), read_size < 0){<br>
:::: +                return -1;<br>:::: +            }<br>:::: +            adding_data(&cbuf->buffer, get_data, cbuf->buf_size,<br>:::: +                                read_size);<br>:::: +            cbuf->buf_size += read_size;<br>
:::: +            head = hg_head(cbuf->handle);<br>:::: +            free(get_data);<br>:::: +        }<br>:::: +    }<br>:::: +    /* After, receiveing the last message, there still could be some<br>:::: +     * csets on cset pointer. */<br>
:::: +    if(cbuf->buffer && strlen(cbuf->buffer + 1) == cbuf->buf_size -1){<br><br>A rather obscure condition to say<br>"in the first (cbuf->buf_size) bytes I don't have any \0 character".<br>
Please make things more explicit, and please don't use strlen as "null byte detector".<br><br><br>:::: +        cbuf->first_cset_size = strlen(cbuf->buffer + 1) + 1;<br>:::: +        parse_changeset(cbuf->buffer + 1, centry);<br>
:::: +        cbuf->buf_size = 0;<br>:::: +        cbuf->is_send = 0;<br>:::: +        return head.length;<br>:::: +    /* Parse first cset from the remaining data. */<br>:::: +    }else if(cbuf->buf_size && cbuf->is_send){<br>
:::: +        cbuf->first_cset_size = strlen(cbuf->buffer + 1) + 1;<br>:::: +        parse_changeset(cbuf->buffer + 1, centry);<br>:::: +        cbuf->is_send = 1;<br>:::: +        return head.length;<br>:::: +    }<br>
:::: +<br>:::: +    exitcode = hg_exitcode(cbuf->handle);<br>:::: +    free(cbuf->command);<br>:::: +    free(cbuf->buffer);<br>:::: +    free(cbuf);<br>:::: +    return exitcode;<br><br>Ok this must be a leftover from previous iterations; a few lines above you're<br>
returning a byte length, here you return a status code. More discipline please.<br>More over, hg_exitcode() will be called by user code, not by this function.<br><br>:::: +<br>:::: +}<br>:::: diff --git a/client.h b/client.h<br>
:::: --- a/client.h<br>:::: +++ b/client.h<br>:::: @@ -69,6 +69,24 @@<br>::::      char *out_data;<br>::::  } hg_handle;<br>:::: <br>:::: +typedef struct hg_csetstream_buffer{<br>:::: +    hg_handle *handle;<br>:::: +    char **command;<br>
:::: +    char *buffer;<br>:::: +    int buf_size;<br>:::: +    int is_send;<br><br></div>After my remark above, I don't think you need this flag.<br></div>It should be spelled "is_sent", anyway (note the `t`).<br>
<div><div><br>:::: +    int first_cset_size;<br>:::: +}hg_csetstream_buffer;<br>:::: +<br>:::: +typedef struct hg_cset_entry{<br>:::: +    char *author;<br>:::: +    char *branch;<br>:::: +    char *date;<br>:::: +    char *desc;<br>
:::: +    char *node;<br>:::: +    char *rev;<br>:::: +    char *tags;<br>:::: +}hg_cset_entry;<br>:::: <br>::::  /**<br>::::   * \brief Open the connection with the mercurial command server.<br>:::: @@ -215,4 +233,63 @@<br>
::::   * */<br>::::  int hg_exitcode(hg_handle *handle);<br>:::: <br>:::: +/**<br>:::: + * \brief hg_log command for hglib API.<br>:::: + *<br>:::: + * It's an advance function to get revision history. It's more like the start<br>
:::: + * point of the action, this function will prepare the query question and will<br>:::: + * send it to the cmd-server.<br>:::: + *<br>:::: + * Return the revision history of the specified files or the entire project.<br>
:::: + * File history is shown without following rename or copy history of files.<br>:::: + * Use follow with a filename to follow history across renames and copies.<br>:::: + * follow without a filename will only show ancestors or descendants of the<br>
:::: + * starting revision. followfirst only follows the first parent of merge<br>:::: + * revisions.<br>:::: + *<br>:::: + * If revrange isn't specified, the default is "tip:0" unless follow is set,<br>:::: + * in which case the working directory parent is used as the starting<br>
:::: + * revision.<br>:::: + *<br>:::: + * \param handle The handle of the connection, wherewith I want to communicate<br>:::: + * \param option The option list for mercurial log command.<br>:::: + * \retval hg_csetstream_buffer A pointer to hg_csetstream_buffer structure if<br>
:::: + *                              successful<br>:::: + * \retval NULL to indicate an error, with errno set appropriately.<br>:::: + *<br>:::: + * errno can be:<br>:::: + *      - hg_rawcommand errors<br>:::: + * */<br>
:::: +hg_csetstream_buffer *hg_log(hg_handle *handle, char *option[]);<br>:::: +<br>:::: +/**<br>:::: + * \brief The iterator step. Getting the next changeset.<br>:::: + *<br>:::: + * The revision history could have a huge mass of data. You cannot pass the<br>
:::: + * entire  history in one call, so we use an iterator-like mechanism. Calling<br>:::: + * the hg_fetch_log_entry. The next changeset will be read from cmd-server,<br>:::: + * parse and pass to hg_cset_entry structure.<br>
:::: + * The cset_entry structure will handle a  changeset with the following string<br>:::: + * fields:<br>:::: + *         - rev<br>:::: + *         - node<br>:::: + *         - tags (space delimited)<br>:::: + *         - branch<br>
:::: + *         - author<br>:::: + *         - desc<br>:::: + *<br>:::: + * \param hg_csetstream_buffer The buffer structure to store cset data.<br>:::: + * \param centry The hg_cset_entry structure where the changeset will be stored<br>
:::: + *               and pass<br>:::: + * \retval number The lenght for the pass changeset.<br>:::: + * \retval exitcode To indicate the end of current_command.<br>:::: + * \retval   -1 to indicate an error, with errno set appropriately.<br>
:::: + *<br>:::: + * errno can be:<br>:::: + *      - EINVAL  - Invalid argument (handle it's set to a null pointer)<br>:::: + *      - read(2) command errors<br>:::: + *      - read_header error<br>:::: + * */<br>:::: +int hg_fetch_cset_entry(hg_csetstream_buffer *cbuf, hg_cset_entry *centry);<br>
:::: +<br>::::  #endif<br>:::: diff --git a/main.c b/main.c<br>:::: new file mode 100644<br>:::: --- /dev/null<br>:::: +++ b/main.c<br>:::: @@ -0,0 +1,128 @@<br>:::: +#include <stdio.h><br>:::: +#include <stdlib.h><br>
:::: +#include <string.h><br>:::: +<br>:::: +#include <sys/wait.h><br>:::: +#include <sys/types.h><br>:::: +#include <unistd.h><br>:::: +#include <sys/stat.h><br>:::: +#include <fcntl.h><br>
:::: +<br>:::: +#include "client.h"<br>:::: +#include "utils.h"<br>:::: +<br>:::: +#define INIT_REPO  "init_test_repo"<br>:::: +<br>:::: +/****** Convenience functions. *******/<br>:::: +<br>
:::: +/**<br>:::: + * \brief Create and setup the tmp directory where the acction will happends.<br>:::: + * */<br>:::: +void setup_tmp()<br>:::: +{<br>:::: +    system("hg init tmp");<br>:::: +    chdir("tmp");<br>
:::: +}<br>:::: +<br>:::: +/**<br>:::: + * \brief Remove the tmp directory and all his files.<br>:::: + * */<br>:::: +void clean_tmp()<br>:::: +{<br>:::: +    chdir("..");<br>:::: +    system("rm -rf tmp");<br>
:::: +}<br>:::: +<br>:::: +/**<br>:::: + * \brief Fill the current repository with commits for log command.<br>:::: + * */<br>:::: +void setup_log()<br>:::: +{<br>:::: +    system("touch foo ; hg add foo ; hg commit -m 'foo file'");<br>
:::: +    system("echo baloo > foo ; hg commit -m 'baloo text'");<br>:::: +    system("touch voodoo ; hg add voodoo ; hg commit -m voodoo");<br>:::: +    system("echo voodoo > voodoo ; hg commit -m 'voodoo text'");<br>
:::: +}<br>:::: +<br>:::: +/******* Examples using level 1 implementations. ******/<br>:::: +<br>:::: +/**<br>:::: + * \brief Log command example.<br>:::: + *<br>:::: + * \param handle The handle of the connection, wherewith I want to communicate<br>
:::: + * \retval exitcode<br>:::: + * */<br>:::: +int hg_log_example(hg_handle *handle)<br>:::: +{<br>:::: +    char *option[] = {"-v", NULL};<br>:::: +    int nc;<br>:::: +<br>:::: +    /* hg_log function will a iterator. */<br>
:::: +    hg_csetstream_buffer *log_iterator = hg_log(handle, option);<br>:::: +<br>:::: +    /* you need to alloc some space for log_entry_t structure */<br>:::: +    hg_cset_entry *le = malloc(sizeof(hg_cset_entry));<br>
:::: +<br>:::: +    /* Getting the next changeset using the iterator-like mechanism.<br>:::: +       Print the changest from log_entry structure.*/<br>:::: +    while(nc = hg_fetch_cset_entry(log_iterator, le), nc > 0){<br>
:::: +        printf("rev = %s \n", le->rev);<br>:::: +        printf("node = %s \n", le->node);<br>:::: +        printf("tags = %s \n", le->tags);<br>:::: +        printf("branch = %s \n", le->branch);<br>
:::: +        printf("author = %s \n", le->author);<br>:::: +        printf("date = %s \n", le->date);<br>:::: +        printf("desc = %s \n", le->desc);<br>:::: +        printf("\n");<br>
:::: +    }<br>:::: +<br>:::: +    free(le);<br>:::: +    /* last call for hg_fetch_log_entry will pass the exitcode */<br>:::: +    return nc;<br>:::: +}<br>:::: +<br>:::: +/** \brief Printing the welcome message.<br>:::: + *<br>
:::: + * Will print the options that you will have in this example.<br>:::: + **/<br>:::: +void print_select_case()<br>:::: +{<br>:::: +    printf("Select test case to run:\n");<br>:::: +    printf("1) log \n");<br>
:::: +    printf("\n");<br>:::: +    printf("Your choice: ");<br>:::: +}<br>:::: +<br>:::: +<br>:::: +<br>:::: +/***** Main function. *******/<br>:::: +/**<br>:::: + * \brief The main function<br>:::: + * */<br>
:::: +int main(int argc, char **argv)<br>:::: +{<br>:::: +    int select_case;<br>:::: +    hg_handle *handle;<br>:::: +<br>:::: +    print_select_case();<br>:::: +    scanf("%d", &select_case);<br>:::: +    if(select_case < 1 || select_case > 1){<br>
:::: +        printf("Your choice is not an option...\n");<br>:::: +        return -1;<br>:::: +    }<br>:::: +<br>:::: +    switch(select_case){<br>:::: +        case 1:<br>:::: +            setup_tmp();<br>:::: +            setup_log();<br>
:::: +            handle = hg_open(NULL, "");<br>:::: +<br>:::: +            hg_log_example(handle);<br>:::: +<br>:::: +            hg_close(&handle);<br>:::: +            clean_tmp();<br>:::: +            break;<br>
:::: +    }<br>:::: +<br>:::: +    return 0;<br>:::: +}<br><br><br>cheers,<br>GGhh<br><br></div></div></div>