[PATCH 2 of 2 RFC] c-hglib: hg_log() level 1 function
Iulian Stana
julian.stana at gmail.com
Sun Aug 18 07:28:09 CDT 2013
# HG changeset patch
# User Iulian Stana <julian.stana at gmail.com>
# Date 1376828454 -10800
# Sun Aug 18 15:20:54 2013 +0300
# Node ID 04bf311c99b8f4cf4c3e2096b493ee126f861b99
# Parent 21be7b973803e02b0bb310465691f88705a2dd53
c-hglib: hg_log() level 1 function
The revision history could have a huge mass of data. To deal with this issue, I
had created a iterator-like mechanism. In this way I will get the changesets in
chunks or more-like one at the time.
The hg_log function will prepare the command and then will call cmd-server for
changesets. This function will return to the user a iterator structure, to be
used on hg_fetch_log_entry funciton. (The log command will not passing any
changeset to the user)
The hg_fetch_log_entry function will read changesets from command server and
will pass into the hg_log_entry_t structure in a parse way the changeset.
The hg_log_entry_t structure will be one of the function parameter.
In the main.c file it can be found an example on how this function can be used.
diff --git a/client.c b/client.c
new file mode 100644
--- /dev/null
+++ b/client.c
@@ -0,0 +1,134 @@
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <signal.h>
+
+
+#include "client.h"
+#include "utils.h"
+
+#define HGPATH "hg"
+#define CHANGESET "{rev}\\0{node}\\0{tags}\\0{branch}\\0{author}\\0{desc}\
+ \\0{date|isodate}\\0"
+
+/**
+ * \brief A helper for building the command arguments.
+ * \param command The name for mercurial command.
+ * \param options An array pointer to command option list.
+ * \param template Template to be used for this command.
+ * \retval new_string An array of pointers, where the command it's almost ready
+ * to be send to mercurial cmdserver.
+ * */
+char **cmdbuilder(char *command, char *options[], char *template)
+{
+ int cmd_size, size;
+ char **new_cmd;
+ char *temp = "--template";
+
+ for(size = 0; options[size] != NULL; size++);
+ cmd_size = size + 1;
+ new_cmd = malloc(sizeof(char *) * (cmd_size + 3));
+
+ new_cmd[0] = command;
+ memcpy(new_cmd + 1, options, (cmd_size - 1) * sizeof(char *));
+ if(template){
+ new_cmd[cmd_size] = temp;
+ new_cmd[cmd_size + 1] = template;
+ new_cmd[cmd_size + 2] = NULL;
+ }else{
+ new_cmd[cmd_size] = NULL;
+ }
+
+ return new_cmd;
+}
+
+/**
+ * \brief 'Parse a changeset'. It's more like pointing to the correct position.
+ *
+ * The changeset could be found on buff pointer. To not duplicate the data I
+ * choose to point every log_entry field to the right position.
+ * \param buff The pointer to the changeset.
+ * \param le The log_entry_t structure where the changeset will be parse.
+ * \retval 0 if successful.
+ * */
+int parse_changeset(char *buff, hg_log_entry_t *le)
+{
+ /* set pointer for revision position */
+ le->rev = buff;
+
+ /* set pointer for node position */
+ buff = buff + strlen(buff) + 1;
+ le->node = buff;
+
+ /* set pointer for tag position */
+ buff = buff + strlen(buff) + 1;
+ le->tags = buff;
+
+ /* set pointer for branch position */
+ buff = buff + strlen(buff) + 1;
+ le->branch = buff;
+
+ /* set pointer for author position */
+ buff = buff + strlen(buff) + 1;
+ le->author = buff;
+
+ /* set pointer for description position */
+ buff = buff + strlen(buff) + 1;
+ le->desc = buff;
+
+ /* set pointer for data position */
+ buff = buff + strlen(buff) + 1;
+ le->date = buff;
+
+ return 0;
+}
+
+/* The high level log command for hglib API. */
+hg_log_iterator *hg_log(hg_handle *handle, char *option[])
+{
+ hg_log_iterator *log_iterator = malloc(sizeof(hg_log_iterator));
+ log_iterator->handle = handle;
+
+ log_iterator->command = cmdbuilder("log", option, CHANGESET);
+
+ if(hg_rawcommand(handle, log_iterator->command) < 0){
+ return NULL;
+ }
+
+ log_iterator->changeset = malloc(0);
+ return log_iterator;
+}
+
+/* The iterator next step. Getting the next changeset. */
+int hg_fetch_log_entry(hg_log_iterator *log_iterator, hg_log_entry_t *le)
+{
+ hg_header head = hg_head(log_iterator->handle);
+
+ if(head.channel == 'r'){
+ free(log_iterator->command);
+ free(log_iterator->changeset);
+ int exitcode = hg_exitcode(log_iterator->handle);
+ free(log_iterator);
+ return exitcode;
+ }
+
+ log_iterator->changeset = realloc(log_iterator->changeset, head.length);
+
+ if(read(log_iterator->handle->p_read, log_iterator->changeset,
+ head.length) < 0){
+ return -1;
+ }
+
+ parse_changeset(log_iterator->changeset, le);
+
+ if(read_header(log_iterator->handle) < 0){
+ return -1;
+ }
+ return head.length;
+}
diff --git a/client.h b/client.h
--- a/client.h
+++ b/client.h
@@ -65,6 +65,22 @@
int protect;
} hg_handle;
+typedef struct hg_log_iterator{
+ hg_handle *handle;
+ char **command;
+ char *changeset;
+}hg_log_iterator;
+
+typedef struct hg_log_entry_t{
+ char *author;
+ char *branch;
+ char *date;
+ char *desc;
+ char *node;
+ char *rev;
+ char *tags;
+}hg_log_entry_t;
+
/**
* \brief Open the connection with the mercurial command server.
*
@@ -196,6 +212,63 @@
* */
int hg_exitcode(hg_handle *handle);
+/**
+ * \brief hg_log command for hglib API.
+ *
+ * It's an advance function to get revision history. It's more like the start
+ * point of the action, this function will prepare the query question and will
+ * send it to the cmd-server.
+ *
+ * Return the revision history of the specified files or the entire project.
+ * File history is shown without following rename or copy history of files.
+ * Use follow with a filename to follow history across renames and copies.
+ * follow without a filename will only show ancestors or descendants of the
+ * starting revision. followfirst only follows the first parent of merge
+ * revisions.
+ *
+ * If revrange isn't specified, the default is "tip:0" unless follow is set,
+ * in which case the working directory parent is used as the starting
+ * revision.
+ *
+ * \param handle The handle of the connection, wherewith I want to communicate
+ * \param option The option list for mercurial log command.
+ * \retval hg_log_iterator A pointer to hg_log_iterator structure if successful
+ * \retval NULL to indicate an error, with errno set appropriately.
+ *
+ * errno can be:
+ * - hg_rawcommand errors
+ * */
+hg_log_iterator *hg_log(hg_handle *handle, char *option[]);
+
+/**
+ * \brief The iterator step. Getting the next changeset.
+ *
+ * The revision history could have a huge mass of data. You can pass the entire
+ * history in one call, so we use an iterator-like mechanism. Calling the
+ * hg_fetch_log_entry, the next changeset will be read from cmd-server, parse
+ * and pass to hg_log_entry_t structure.
+ * The log_entry structure will handle a changeset with the following string
+ * fields:
+ * - rev
+ * - node
+ * - tags (space delimited)
+ * - branch
+ * - author
+ * - desc
+ *
+ * \param log_iterator The iterator for log command.
+ * \param log_entry The hg_log_entry_t structure where the changeset will be
+ * pass
+ * \retval number The lenght for the pass changeset.
+ * \retval exitcode To indicate the end of log_command.
+ * \retval -1 to indicate an error, with errno set appropriately.
+ *
+ * errno can be:
+ * - EINVAL - Invalid argument (handle it's set to a null pointer)
+ * - read(2) command errors
+ * - read_header error
+ * */
+int hg_fetch_log_entry(hg_log_iterator *log_iterator, hg_log_entry_t *le);
#endif
diff --git a/main.c b/main.c
new file mode 100644
--- /dev/null
+++ b/main.c
@@ -0,0 +1,129 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "client.h"
+#include "utils.h"
+
+#define INIT_REPO "init_test_repo"
+
+/****** Convenience functions. *******/
+
+/**
+ * \brief Create and setup the tmp directory where the acction will happends.
+ * */
+void setup_tmp()
+{
+ system("hg init tmp");
+ chdir("tmp");
+}
+
+/**
+ * \brief Remove the tmp directory and all his files.
+ * */
+void clean_tmp()
+{
+ chdir("..");
+ system("rm -rf tmp");
+}
+
+/**
+ * \brief Fill the current repository with commits for log command.
+ * */
+void setup_log()
+{
+ system("touch foo ; hg add foo ; hg commit -m 'foo file'");
+ system("echo baloo > foo ; hg commit -m 'baloo text'");
+ system("touch voodoo ; hg add voodoo ; hg commit -m voodoo");
+ system("echo voodoo > voodoo ; hg commit -m 'voodoo text'");
+}
+
+/******* Examples using level 1 implementations. ******/
+
+/**
+ * \brief Log command example.
+ *
+ * \param handle The handle of the connection, wherewith I want to communicate
+ * \retval exitcode
+ * */
+int hg_log_example(hg_handle *handle)
+{
+ char *option[] = {"-v", NULL};
+ int nc;
+
+ /* hg_log function will a iterator. */
+ hg_log_iterator *log_iterator = hg_log(handle, option);
+
+ /* you need to alloc some space for log_entry_t structure */
+ hg_log_entry_t *le = malloc(sizeof(hg_log_entry_t));
+
+ /* Getting the next changeset using the iterator-like mechanism.
+ Print the changest from log_entry structure.*/
+ while(nc = hg_fetch_log_entry(log_iterator, le), nc > 0){
+ printf("rev = %s \n", le->rev);
+ printf("node = %s \n", le->node);
+ printf("tags = %s \n", le->tags);
+ printf("branch = %s \n", le->branch);
+ printf("author = %s \n", le->author);
+ printf("desc = %s \n", le->desc);
+ printf("date = %s \n", le->date);
+ printf("\n");
+ }
+
+ free(le);
+ /* last call for hg_fetch_log_entry will pass the exitcode */
+ return nc;
+}
+
+/** \brief Printing the welcome message.
+ *
+ * Will print the options that you will have in this example.
+ **/
+void print_select_case()
+{
+ printf("Select test case to run:\n");
+ printf("1) log \n");
+ printf("\n");
+ printf("Your choice: ");
+}
+
+
+/***** Main function. *******/
+/**
+ * \brief The main function
+ * */
+int main(int argc, char **argv)
+{
+ int select_case;
+ hg_handle *handle;
+
+ print_select_case();
+ scanf("%d", &select_case);
+ if(select_case < 1 || select_case > 1){
+ printf("Your choice is not an option...\n");
+ return -1;
+ }
+
+ switch(select_case){
+ case 1:
+ setup_tmp();
+ setup_log();
+ handle = hg_open(NULL, "");
+
+ hg_log_example(handle);
+
+ hg_close(&handle);
+ clean_tmp();
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
More information about the Mercurial-devel
mailing list