<div dir="ltr"><div class="gmail_extra"><br><br><div class="gmail_quote">2013/8/18 Iulian Stana <span dir="ltr"><<a href="mailto:julian.stana@gmail.com" target="_blank">julian.stana@gmail.com</a>></span><br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
# HG changeset patch<br>
# User Iulian Stana <<a href="mailto:julian.stana@gmail.com">julian.stana@gmail.com</a>><br>
# Date 1376828454 -10800<br>
#      Sun Aug 18 15:20:54 2013 +0300<br>
# Node ID 04bf311c99b8f4cf4c3e2096b493ee126f861b99<br>
# Parent  21be7b973803e02b0bb310465691f88705a2dd53<br>
c-hglib: hg_log() level 1 function<br>
<br>
The revision history could have a huge mass of data. To deal with this issue, I<br>
had created a iterator-like mechanism. In this way I will get the changesets in<br>
chunks or more-like one at the time.<br>
<br>
The hg_log function will prepare the command and then will call cmd-server for<br>
changesets. This function will return to the user a iterator structure, to be<br>
used on hg_fetch_log_entry funciton. (The log command will not passing any<br>
changeset to the user)<br>
<br>
The hg_fetch_log_entry function will read changesets from command server and<br>
will pass into the hg_log_entry_t structure in a parse way the changeset.<br>
The hg_log_entry_t structure will be one of the function parameter.<br>
<br>
In the main.c file it can be found an example on how this function can be used.<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,134 @@<br>
+#define _GNU_SOURCE<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 "{rev}\\0{node}\\0{tags}\\0{branch}\\0{author}\\0{desc}\<br>
+                                               \\0{date|isodate}\\0"<br>
+<br>
+/**<br>
+ * \brief A helper for building the command arguments.<br>
+ * \param command The name for mercurial command.<br>
+ * \param options An array pointer to command option list.<br>
+ * \param template Template to be used for this command.<br>
+ * \retval new_string An array of pointers, where the command it's almost ready<br>
+ *                    to be send to mercurial cmdserver.<br>
+ * */<br>
+char **cmdbuilder(char *command, char *options[], char *template)<br>
+{<br>
+       int cmd_size, size;<br>
+       char **new_cmd;<br>
+       char *temp = "--template";<br>
+<br>
+       for(size = 0; options[size] != NULL; size++);<br>
+       cmd_size = size + 1;<br>
+       new_cmd = malloc(sizeof(char *) * (cmd_size + 3));<br>
+<br>
+       new_cmd[0] = command;<br>
+       memcpy(new_cmd + 1, options, (cmd_size - 1)  * sizeof(char *));<br>
+       if(template){<br>
+               new_cmd[cmd_size] = temp;<br>
+               new_cmd[cmd_size + 1] = template;<br>
+               new_cmd[cmd_size + 2] = NULL;<br>
+       }else{<br>
+               new_cmd[cmd_size] = NULL;<br>
+       }<br>
+<br>
+       return new_cmd;<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 log_entry field to the right position.<br>
+ * \param buff The pointer to the changeset.<br>
+ * \param le   The log_entry_t structure where the changeset will be parse.<br>
+ * \retval 0 if successful.<br>
+ * */<br>
+int parse_changeset(char *buff, hg_log_entry_t *le)<br>
+{<br>
+       /* set pointer for revision position */<br>
+       le->rev = buff;<br>
+<br>
+       /* set pointer for node position */<br>
+       buff = buff + strlen(buff) + 1;<br>
+       le->node = buff;<br>
+<br>
+       /* set pointer for tag position */<br>
+       buff = buff + strlen(buff) + 1;<br>
+       le->tags = buff;<br>
+<br>
+       /* set pointer for branch position */<br>
+       buff = buff + strlen(buff) + 1;<br>
+       le->branch = buff;<br>
+<br>
+       /* set pointer for author position */<br>
+       buff = buff + strlen(buff) + 1;<br>
+       le->author = buff;<br>
+<br>
+       /* set pointer for description position */<br>
+       buff = buff + strlen(buff) + 1;<br>
+       le->desc = buff;<br>
+<br>
+       /* set pointer for data position */<br>
+       buff = buff + strlen(buff) + 1;<br>
+       le->date = buff;<br>
+<br>
+       return 0;<br>
+}<br>
+<br>
+/* The high level log command for hglib API. */<br>
+hg_log_iterator *hg_log(hg_handle *handle, char *option[])<br>
+{<br>
+       hg_log_iterator *log_iterator = malloc(sizeof(hg_log_iterator));<br>
+       log_iterator->handle = handle;<br>
+<br>
+       log_iterator->command = cmdbuilder("log", option, CHANGESET);<br>
+<br>
+       if(hg_rawcommand(handle, log_iterator->command) < 0){<br>
+               return NULL;<br>
+       }<br>
+<br>
+       log_iterator->changeset = malloc(0);<br>
+       return log_iterator;<br></blockquote><div><br></div><div><br>What happens here is that hg_log() does not return a status code<br>(with the meaning "command successfully sent"), but the hg_log_iterator struct instead.<br>
<br></div><div>A status code would be desirable and idiomatic I think;<br></div><div>the struct to be passed as an argument.<br></div><div><br> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">

+}<br>
+<br>
+/* The iterator next step. Getting the next changeset. */<br>
+int hg_fetch_log_entry(hg_log_iterator *log_iterator, hg_log_entry_t *le)<br>
+{<br>
+       hg_header head = hg_head(log_iterator->handle);<br>
+<br>
+       if(head.channel == 'r'){<br>
+               free(log_iterator->command);<br>
+               free(log_iterator->changeset);<br>
+               int exitcode = hg_exitcode(log_iterator->handle);<br>
+               free(log_iterator);<br>
+               return exitcode;<br>
+       }<br>
+<br>
+       log_iterator->changeset = realloc(log_iterator->changeset, head.length);<br>
+<br>
+       if(read(log_iterator->handle->p_read, log_iterator->changeset,<br>
+                                       head.length) < 0){<br></blockquote><div><br><br></div><div>Here you are not using hg_rawread(). Please explain why.<br></div><div>Level 1 functions are meant to build on top of level 0 functions.<br>
<br></div><div>Also: you are not reading in 4096 B chunks.<br></div><div><br></div><div>Also: you are assuming that all information for a given log entry<br></div><div>(a revision) are retrieved in a "single shot" of the  'o' channel.<br>
<br></div><div>You are assuming that a log revision cannot be split across two<br></div><div>iteration of output on the 'o' channel.<br><br></div><div>Is this assumption safe?<br><br></div><div>Please ask Idan Kamara (CC the list) and make sure of this.<br>
</div><div><br> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
+               return -1;<br>
+       }<br>
+<br>
+       parse_changeset(log_iterator->changeset, le);<br>
+<br>
+       if(read_header(log_iterator->handle) < 0){<br>
+               return -1;<br>
+       }<br>
+       return head.length;<br>
+}<br>
diff --git a/client.h b/client.h<br>
--- a/client.h<br>
+++ b/client.h<br>
@@ -65,6 +65,22 @@<br>
        int protect;<br>
 } hg_handle;<br>
<br>
+typedef struct hg_log_iterator{<br></blockquote><div><br><br></div><div>I think this name is unfortunate. This is not an iterator, in any possible sense.<br></div><div>This is the place where you store your data (the "changeset" field of this structure).<br>
<br></div><div>I would call this something like hg_log_rawentry_t, in contrast to the<br></div><div>other structure where the log entry is "parsed" and not "raw".<br><br></div><div>Please note that your code works as an iterator _not_ because of this struct<br>
</div><div>(where you are storing a single element of your possibly "infinite" list),<br></div><div>_but_ because it all piggybacks on the syscall read(2), which works as an iterator itself.<br></div><div><br> </div>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
+       hg_handle *handle;<br>
+       char **command;<br>
+       char *changeset;<br>
+}hg_log_iterator;<br>
+<br>
+typedef struct hg_log_entry_t{<br>
+       char *author;<br>
+       char *branch;<br>
+       char *date;<br>
+       char *desc;<br>
+       char *node;<br>
+       char *rev;<br>
+       char *tags;<br>
+}hg_log_entry_t;<br>
+<br>
 /**<br>
  * \brief Open the connection with the mercurial command server.<br>
  *<br>
@@ -196,6 +212,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_log_iterator A pointer to hg_log_iterator structure if successful<br>
+ * \retval NULL to indicate an error, with errno set appropriately.<br>
+ *<br>
+ * errno can be:<br>
+ *      - hg_rawcommand errors<br>
+ * */<br>
+hg_log_iterator *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 can pass the entire<br>
+ * history in one call, so we use an iterator-like mechanism. Calling the<br>
+ * hg_fetch_log_entry, the next changeset will be read from cmd-server, parse<br>
+ * and pass to hg_log_entry_t structure.<br>
+ * The log_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 log_iterator The iterator for log command.<br>
+ * \param log_entry The hg_log_entry_t structure where the changeset will be<br>
+ *                   pass<br>
+ * \retval number The lenght for the pass changeset.<br>
+ * \retval exitcode To indicate the end of log_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_log_entry(hg_log_iterator *log_iterator, hg_log_entry_t *le);<br>
<br>
 #endif<br>
<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,129 @@<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_log_iterator *log_iterator = hg_log(handle, option);<br>
+<br>
+       /* you need to alloc some space for log_entry_t structure */<br>
+       hg_log_entry_t *le = malloc(sizeof(hg_log_entry_t));<br>
+<br>
+       /* Getting the next changeset using the iterator-like mechanism.<br>
+          Print the changest from log_entry structure.*/<br>
+       while(nc = hg_fetch_log_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("desc = %s \n", le->desc);<br>
+               printf("date = %s \n", le->date);<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>
+/***** 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>
+               default:<br>
+                       break;<br>
+       }<br>
+<br>
+       return 0;<br>
+}<br>
_______________________________________________<br>
Mercurial-devel mailing list<br>
<a href="mailto:Mercurial-devel@selenic.com">Mercurial-devel@selenic.com</a><br>
<a href="http://selenic.com/mailman/listinfo/mercurial-devel" target="_blank">http://selenic.com/mailman/listinfo/mercurial-devel</a><br>
</blockquote></div><br></div></div>