[PATCH 2 of 3 RFC -V3] Implementation of basic hg commands via low level API (stories)

Iulian Stana julian.stana at gmail.com
Thu Jul 25 12:27:46 CDT 2013


# HG changeset patch
# User Iulian Stana <julian.stana at gmail.com>
# Date 1374753623 -10800
#      Thu Jul 25 15:00:23 2013 +0300
# Node ID a97753ae4036ba4ea34ccb67971d31c01f97ed4b
# Parent  2cc0845928f01bf161cb1d4983bd029c7c3e70aa
Implementation of basic hg commands via low level API (stories)

The main.c file is not “real code”, just a showcase of examples. It is more of a
documentation than actual code. It contains the "stories" that Matt asked me to
prepare. Here I created some scenarios to test those stories.

Those examples stands with purpose to show that you can implement and handle in
some ways all the mercurial commands, using just the level 0 of c-hglib API.

a) The init command, which starts with no repository. For this story I use the
following approach: first I create a new process where I will execute the init
command with plain hg, and after the child ends I establish the connection
with the new repository.

b) The log command, which can produce huge data. The data is read in chunks. In
my example I just print this data to the standard output, but the user is free
to do what ever he wants with this data. (write it into a file or process it)

c) The import command, which can deal with the command server input channels. On
this task, I was able to show that you can import a patch using stdin. In the
same time you can specify a file to simulate the stdin process.

d) The merge command, which can deal prompts. Sometimes when a conflict appears,
the merge command could use those prompts to interact with user desires.
In my function I gave an example on how a user could pass a prompt procedure, a
procedure where the interation is handled. (in my procedure function the answer
will be received from stdin)

e) The verify command, where the command server could send output and error
data. The received data will be split in two. You will know the type of data
(output data or error data)  with the channel help, who can be found in the
header structure of the handle.

f) Export-import without saving data into ram or disk memory. There will be two
connections, one for the export repository (export handle) and one for the
import repository ( import handle). Immediately after you receive any kind of
data from the export handle, you will pass it to the import handle.

g) Add 'filename with spaces' example.

All of those examples are build with level 0 function witout using fancy
functions.
I refer to those implementation as 'by hand' ones, as opposed to 'using a lot of
technology and abstraction.

diff --git a/main.c b/main.c
new file mode 100644
--- /dev/null
+++ b/main.c
@@ -0,0 +1,444 @@
+#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. *******/
+
+/* Setup the tmp directory where the magic happends.*/
+void setup_tmp()
+{
+	system("hg init tmp");
+	chdir("tmp");
+}
+
+void clean_tmp()
+{
+	chdir("..");
+	system("rm -rf tmp");
+}
+
+/* Setup for init command. */
+void setup_init()
+{
+	system("mkdir tmp");
+	chdir("tmp");
+}
+
+/* Create some commits in the created repo. */
+void post_init_setup()
+{
+	chdir(INIT_REPO);
+	system("touch foo ; hg add foo ; hg commit -m foo");
+	system("echo baloo > foo ; hg commit -m 'baloo text'");
+	chdir("..");
+}
+
+/* Setup for log command. */
+void setup_log()
+{
+	system("touch foo ; hg add foo ; hg commit -m foo");
+	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'");
+}
+
+/* Setup the merge command. Without conflicts.*/
+void setup_merge()
+{
+	system("touch foo ; hg add foo ; hg commit -m foo");
+	system("echo baloo > foo ; hg commit -m 'baloo text'");
+	system("hg up 0");
+	system("touch boo ; hg add boo ; hg commit -m boo");
+	system("echo voodoo > boo ; hg commit -m 'voodoo text'");
+}
+
+/* Setup the merge command. Create a conflict that will have prompts.*/
+void setup_conflict_merge()
+{
+	system("touch foo ; hg add foo ; hg commit -m foo");
+	system("echo baloo > foo ; hg commit -m 'baloo text'");
+	system("hg up 0");
+	system("echo voodoo > foo ; hg commit -m 'voodoo text'");
+}
+
+/* Setup for export_import process.*/
+void setup_export_import()
+{
+	system("hg init export");
+	chdir("export");
+	system("touch foo ; echo baloo > foo; hg add foo ; hg commit -m foo");
+	chdir("..");
+	system("hg init import");
+}
+
+void clean_export_import()
+{
+	system("rm -rf export import");
+}
+
+/* Setup for filename_with_space example.*/
+void setup_filename_with_space()
+{
+	system("touch 'foo bar'");
+	printf("---- ls command ----\n");
+	system("ls -l");
+}
+
+/******* By hand implementations. ******/
+
+/*
+ * Create a repo to the specific path, and then will open the connection with 
+ * this new repo.
+ * The clone command will follow the same steps. Clone repo; open connection.
+ * */
+hg_handle *hg_init_by_hand()
+{
+	pid_t cpid;
+	int status;
+	hg_handle *handle;
+	char command[50];
+
+	sprintf(command, "hg init %s", INIT_REPO);
+
+	if((cpid = fork()) < 0) {
+		printf("Fork failed\n");
+		return NULL;
+
+	} else if(cpid == 0) {
+		execl("/bin/sh", "sh", "-c", command, NULL);
+		printf("dadads\n\n");
+	} else {
+		 waitpid( cpid, &status, 0);
+	}
+
+	handle = hg_open(INIT_REPO, "");
+
+	return handle;
+}
+
+/*
+ * Will read a huge mass of data in chunks and will print this data on stdout.
+ * */
+int hg_log_by_hand(hg_handle *handle)
+{
+	char buff[4096];
+	char *comm[] = {"log", "-v"};
+	int exitcode;
+	int ns;
+
+	hg_rawcommand(handle, comm, 2);
+
+	while(hg_channel(handle) != 'r'){
+		while(ns = hg_rawread(handle, buff, 4096), ns > 0){
+			printf("%s", buff);
+		}
+	}
+
+	exitcode = hg_exitcode(handle);
+	printf("exitcode = %d\n", exitcode);
+
+	return exitcode;
+}
+
+/*
+ * The purpose is to show the functionality, it's not the import function.
+ * This function will import to the handle the import_patch, which is a file or
+ * will use the stdin if the import_patch is NULL.
+ * */
+int hg_import_by_hand(hg_handle *handle, char *import_patch)
+{
+	char *comm[] = {"import", "-"};
+	char buff[4096];
+	int exitcode = 0;
+	int fd, ns;
+	hg_header header;
+
+	fd = open(import_patch, O_RDONLY);
+	hg_rawcommand(handle, comm, 2);
+
+	while(hg_channel(handle) != 'r'){
+		/* outchannels 'o' or 'e'. */
+		while(ns = hg_rawread(handle, buff, 4096), ns > 0){
+			printf("%s", buff);
+		}
+		if(hg_channel(handle) == 'L'){
+			header = hg_head(handle);
+			int length = read(fd, buff, header.length);
+			hg_rawwrite(handle, buff, length);
+		}
+	}
+
+	exitcode = hg_exitcode(handle);
+	return exitcode;
+}
+
+/* The prompt function for the merge funtion.*/
+char prompt_function(char *output)
+{
+	char option;
+	scanf("\n%c", &option);
+	return option;
+}
+
+/*
+ * The merge function gets a prompt function that deals with promps.
+ * It's just a default function, to show the mechanism(is't not a level 0 issue)
+ * This function show that you can create a merge function that handle prompts
+ * with level 0 API.
+ * */
+int hg_merge_by_hand(hg_handle *handle, char (*prompt)(char *))
+{
+	char *comm[] = {"merge", "--tool=internal:prompt"};
+	char buff[4096];
+	int exitcode = 0;
+	int ns;
+
+	hg_rawcommand(handle, comm, 2);
+
+	while(hg_channel(handle) != 'r'){
+		/* outchannels 'o' or 'e'. */
+		while(ns = hg_rawread(handle, buff, 4096), ns > 0){
+			printf("%s", buff);
+		}
+		if(hg_channel(handle) == 'I'){
+			printf("INPUT\n");
+		}
+		else if(hg_channel(handle) == 'L'){
+			/* In stand of the default prompt_msg, the argument for 
+			 * prompt function will be the output data received
+			 * until this moment.
+			 * */
+			char option = (*prompt)(NULL);
+			printf("\noption = %c\n", option);
+			hg_rawwrite(handle, &option, 1);
+			/* TODO: is a small issue, but not for this level.
+			 * The second write, must not be necessary.
+			 * */
+			hg_rawwrite(handle, &option, 0);
+		}
+	}
+
+	exitcode = hg_exitcode(handle);
+	printf("exitcode = %d\n", exitcode);
+
+	return exitcode;
+}
+
+/*
+ * Will receive data, either from output channel, either from error channel.
+ * The data is printed on stdout. 
+ * */
+int hg_verify_by_hand(hg_handle *handle)
+{
+	char *comm[] = {"verify"};
+	char buff[4096];
+	int exitcode = 0;
+	int ns;
+
+	hg_rawcommand(handle, comm, 1);
+
+	while(hg_channel(handle) != 'r'){
+		while(ns = hg_rawread(handle, buff, 4096), ns > 0){
+			if(hg_channel(handle) == 'o'){
+				printf("out = %s", buff);
+			}
+			else if(hg_channel(handle) == 'e'){
+				printf("err = %s", buff);
+			}
+		}
+	}
+
+	exitcode = hg_exitcode(handle);
+	printf("exitcode = %d\n", exitcode);
+
+	return exitcode;
+}
+
+/* An export-import process that is not buffering the entire path in to memory*/
+void hg_export_import_by_hand(hg_handle *ehandle, hg_handle *ihandle)
+{
+	char *export_comm[] = {"export", "-r", "0"};
+	char *import_comm[] = {"import", "-"};
+	char ebuff[4096], ibuff[4096];
+	int es, is;
+
+	hg_rawcommand(ehandle, export_comm, 3);
+	hg_rawcommand(ihandle, import_comm, 2);
+
+	while(hg_channel(ehandle) != 'r'){
+		while(es = hg_rawread(ehandle, ebuff, 4096), es > 0){
+			while(is = hg_rawread(ihandle, ibuff, 4096), is > 0){
+				printf("%s", ibuff);
+			}
+			if(hg_channel(ihandle) == 'L'){
+				hg_rawwrite(ihandle, ebuff, strlen(ebuff));
+			}
+		}
+	}
+	/* Send a message to ihandle to understand that the import process
+	 * was ending*/
+	hg_rawwrite(ihandle, ebuff, 0);
+
+	while(hg_channel(ihandle) != 'r'){
+		while(is = hg_rawread(ihandle, ibuff, 4096), is > 0){
+			printf("%s", ibuff);
+		}
+	}
+
+	printf("exitcode for export process is %d \n", hg_exitcode(ehandle));
+	printf("exitcode for import process is %d \n", hg_exitcode(ihandle));
+
+}
+
+int hg_add_filename_with_space_by_hand(hg_handle *handle)
+{
+	char buff[4096];
+	char *comm[] = {"add", "foo bar"};
+	int exitcode;
+	int ns;
+
+	hg_rawcommand(handle, comm, 2);
+
+	while(hg_channel(handle) != 'r'){
+		while(ns = hg_rawread(handle, buff, 4096), ns > 0){
+			printf("%s", buff);
+		}
+	}
+
+	exitcode = hg_exitcode(handle);
+	printf("exitcode = %d\n", exitcode);
+
+	return exitcode;
+}
+
+/* Print the start prompt, what choice you have. */
+void print_select_case()
+{
+	printf("Select test case to run:\n");
+	printf("0) init - 'make some commits' & log \n");
+	printf("1) log \n");
+	printf("2) import - from stdin (simulate from a file) \n");
+	printf("3) merge - (solve conflicts from stdin) \n");
+	printf("4) merge - (without conflicts)\n");
+	printf("5) verify \n");
+	printf("6) verify a corrupt repo.\n");
+	printf("7) export-import example.\n");
+	printf("8) add 'filename with space'\n");
+	printf("\n");
+	printf("Your choice: ");
+}
+
+
+/***** Main function. *******/
+
+int main(int argc, char **argv)
+{
+	int select_case;
+	hg_handle *handle;
+	hg_handle *ehandle, *ihandle;
+
+	print_select_case();
+	scanf("%d", &select_case);
+	if(select_case < 0 || select_case > 8){
+		printf("Your choice is not an option...\n");
+		return -1;
+	}
+
+	switch(select_case){
+		case 0:
+			setup_init();
+			handle = hg_init_by_hand();
+			post_init_setup();
+			hg_log_by_hand(handle);
+
+			hg_close(&handle);
+			clean_tmp();
+			break;
+		case 1:
+			setup_tmp();
+			setup_log();
+			handle = hg_open(NULL, "");
+			hg_log_by_hand(handle);
+
+			hg_close(&handle);
+			clean_tmp();
+			break;
+		case 2:
+			setup_export_import();
+			ihandle = hg_open("import", "");
+
+			chdir("export");
+			system("hg export -r 0 > r0.diff");
+			chdir("..");
+			hg_import_by_hand(ihandle, "export/r0.diff");
+
+			printf("\n");
+			hg_log_by_hand(ihandle);
+
+			hg_close(&ihandle);
+			clean_export_import();
+			break;
+		case 3:
+		case 4:
+			setup_tmp();
+			if(select_case == 3)
+				setup_conflict_merge();
+			else
+				setup_merge();
+			handle = hg_open(NULL, "");
+			hg_merge_by_hand(handle, prompt_function);
+			system("hg ci -m 'merge'");
+			system("hg glog");
+
+			hg_close(&handle);
+			clean_tmp();
+			break;
+		case 5:
+		case 6:
+			setup_tmp();
+			setup_log();
+			handle = hg_open(NULL, "");
+			printf("\n");
+			if(select_case == 6)
+				system("rm .hg/store/data/foo.i");
+			hg_verify_by_hand(handle);
+
+			hg_close(&handle);
+			clean_tmp();
+			break;
+		case 7:
+			setup_export_import();
+			ehandle = hg_open("export", "");
+			ihandle = hg_open("import", "");
+			hg_export_import_by_hand(ehandle, ihandle);
+			hg_close(&ehandle);
+			hg_close(&ihandle);
+			clean_export_import();
+			break;
+		case 8:
+			setup_tmp();
+			setup_filename_with_space();
+			printf("\n");
+			handle = hg_open(NULL, "");
+			hg_add_filename_with_space_by_hand(handle);
+			printf("\n---- hs status command ----\n");
+			system("hg status");
+			clean_tmp();
+			break;
+		default:
+			break;
+	}
+
+	return 0;
+}


More information about the Mercurial-devel mailing list