Subject: Re: [PATCH] Geolocation support

From: Alessandro Di Marco <[email-masked]>
Date: Thu, 12 Feb 2015 21:50:42 +0100

Junio C Hamano <[email-masked]> writes:

> Ævar Arnfjörð Bjarmason <[email-masked]> writes:

>> We've already told clients for a long time to ignore fields they
>> don't know about,

> Yes, but the reason that mechanism is there is not because we want to
> add random cruft Git does not have to know about.  It is to avoid
> older Git from suddenly stop working when we really need to add new
> essential things.

I see, so the problem lays in the info at hand (ie. the commit
location), apparently not enough important to be promoted to the upper
floor. Admittedly all of this is difficult to appreciate unless you are
on the move (as well as the need of a date would be probably questioned
by an highlander), therefore I refined the previous approach.

The new patch here below will allow anybody to import any crufts
into the commit header. Simply define an envvar such as:

export GIT_XT_CRUFT=">foo"

and it will place an extra header such as:

xt-cruft foo

in the commit object. I felt free to insert the 'xt-' prefix in order
not to clash with existing and/or future headers of the commit
object. The '>' indicates that the header is not amendable; in case you
want an amendable one simply switch from '>' to '@' as below:

export GIT_XT_CRUFT="@foo"

> More importantly, adding non-essential stuff left and right will force
> third party Git reimplementations to pay attention to them and also
> will leave room for them to make mistakes when deciding what to
> propagate, what to drop and what to update when rewriting commits via
> rebase, cherry-pick, etc.

??? http://en.wikipedia.org/wiki/Security_through_obscurity

Do you realize that every git I tried so far has happily accepted any
crufts I sent to it via git push? And that they stored that crufts and
then returned it on cloning? :-|

Feel free to try the below from your client:

<0> $ git clone [email-masked]:dmrlsn/iwillmeltyourdata.git
Cloning into 'iwillmeltyourdata'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Receiving objects: 100% (3/3), done.
Checking connectivity... done.

<0> $ cd iwillmeltyourdata/

<0> $ git log --pretty=raw | more
commit b704144270528fc6022714861d149441d4102ee9
tree 6bf21934b3b186561626891e2b98f99e6da89e2f
author Alessandro Di Marco <[email-masked]> 1423770339 +0100
committer Alessandro Di Marco <[email-masked]> 1423770339 +0100
xt-committer-location Tokyo, Japan (35.685, 139.7514)
xt-author-location Tokyo, Japan (35.685, 139.7514)

    Minor changes

^^ obtained by defining:

declare -x GIT_XT_AUTHOR_LOCATION=">Tokyo, Japan (35.685, 139.7514)"
declare -x GIT_XT_COMMITTER_LOCATION="@Tokyo, Japan (35.685, 139.7514)"

prior committing.

> I think this sentence gets it backwards.  The question to ask is if it
> is an arbitrary cruft that the end users are better off if they can
> easily typofix in the commit message log editor, or is it essential
> for Git to operate correctly and end users shouldn't be allowed to
> muck with in the editor?

>>> The expected location format is "CITY, COUNTRY (LAT, LON)".

> I would expect that I can typofix "Les Angeles" to "Los Angeles",
> if I were using this feature.

Well, what about a format such as "(LAT, LON)"? Would you expect to
typofix it too? If so, why don't you put the date on the commit message
as well? Your clock could fail, after all...

Signed-off-by: Alessandro Di Marco <[email-masked]>
---
 builtin/commit.c | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 commit.c         |  14 ++++----
 commit.h         |   3 +-
 3 files changed, 114 insertions(+), 11 deletions(-)

diff --git a/builtin/commit.c b/builtin/commit.c
index 7f46713..0ff4aef 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -623,6 +623,100 @@ static int author_date_is_interesting(void)
 	return author_message || force_date;
 }

+static void exclude_free(char ***exclude) {
+	char **p = *exclude;
+	int len;
+
+	for (len = 0; p[len]; len++) {
+		free(p[len]);
+	}
+	free(p);
+	*exclude = NULL;
+}
+
+static void exclude_one(char ***exclude, char *what)
+{
+	char **p = *exclude;
+	int len;
+
+	for (len = 0; p && p[len]; len++);
+	p = *exclude = xrealloc(p, (len + 2) * sizeof (char *));
+	p[len] = xmalloc(strlen(what) + 1);
+	p[len + 1] = NULL;
+	strcpy(p[len], what);
+}
+
+static char *env2extra(char *var)
+{
+	int i, l = strlen(var);
+
+	for (i = 0; i < l; i++) {
+		if (var[i] >= 'A' && var[i] <= 'Z') {
+			var[i] += 32;
+		} else if (var[i] == '_') {
+			var[i] = '-';
+		}
+	}
+	memcpy(var -= 3, "xt-", 3);
+	return var;
+}
+
+static void determine_xt_vars(struct strbuf *xtvars, char ***exclude)
+{
+	extern char **environ;
+	int i;
+
+	for (i = 0; environ[i]; i++) {
+		char *p, *var, *xtvar, *val;
+		int l, amending;
+
+		if (strncmp(environ[i], "GIT_XT_", 7)) {
+			continue;
+		}
+
+		p = strchr(environ[i], '=');
+		if (!p) {
+			continue;
+		}
+
+		l = p - environ[i];
+
+		var = xmalloc(l + 1);
+		memcpy(var, environ[i], l);
+		var[l] = '\0';
+
+		val = getenv(var);
+		if (strlen(val) < 2) {
+			free(var);
+			continue;
+		}
+
+		xtvar = env2extra(&var[7]);
+		amending = amend;
+
+		switch(val[0]) {
+		case '@':
+			if (amending) {
+				exclude_one(exclude, xtvar);
+				amending = 0;
+			}
+			/* fall-through */
+		case '>':
+			if (!amending) {
+				strbuf_addstr(xtvars, xtvar);
+				strbuf_addch(xtvars, ' ');
+				strbuf_addstr(xtvars, val + 1);
+				strbuf_addch(xtvars, '\n');
+			}
+			break;
+		default:
+			/* malformed xtvar, ignore */
+			break;
+		}
+		free(var);
+	}
+}
+
 static void adjust_comment_line_char(const struct strbuf *sb)
 {
 	char candidates[] = "#;@!$%^&|:";
@@ -1624,6 +1718,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)

 	struct strbuf sb = STRBUF_INIT;
 	struct strbuf author_ident = STRBUF_INIT;
+	struct strbuf xtvars = STRBUF_INIT;
 	const char *index_file, *reflog_msg;
 	char *nl;
 	unsigned char sha1[20];
@@ -1663,6 +1758,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 		return 1;
 	}

+	char **exclude = NULL;
+	determine_xt_vars(&xtvars, &exclude);
+
 	/* Determine parents */
 	reflog_msg = getenv("GIT_REFLOG_ACTION");
 	if (!current_head) {
@@ -1739,13 +1837,19 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 	}

 	if (amend) {
-		const char *exclude_gpgsig[2] = { "gpgsig", NULL };
-		extra = read_commit_extra_headers(current_head, exclude_gpgsig);
+		exclude_one(&exclude, "gpgsig");
+		extra = read_commit_extra_headers(current_head, exclude);
+		exclude_free(&exclude);
 	} else {
 		struct commit_extra_header **tail = &extra;
 		append_merge_tag_headers(parents, &tail);
 	}

+	if (xtvars.len > 0) {
+		extra = read_commit_extra_header_lines(xtvars.buf, xtvars.len, NULL, extra);
+	}
+	strbuf_release(&xtvars);
+
 	if (commit_tree_extended(sb.buf, sb.len, active_cache_tree->sha1,
 			 parents, sha1, author_ident.buf, sign_commit, extra)) {
 		rollback_index_files();
diff --git a/commit.c b/commit.c
index a8c7577..48fc4c0 100644
--- a/commit.c
+++ b/commit.c
@@ -12,8 +12,6 @@
 #include "prio-queue.h"
 #include "sha1-lookup.h"

-static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
-
 int save_commit_buffer = 1;

 const char *commit_type = "commit";
@@ -1279,12 +1277,12 @@ static void add_extra_header(struct strbuf *buffer,
 }

 struct commit_extra_header *read_commit_extra_headers(struct commit *commit,
-						      const char **exclude)
+						      char **exclude)
 {
 	struct commit_extra_header *extra = NULL;
 	unsigned long size;
 	const char *buffer = get_commit_buffer(commit, &size);
-	extra = read_commit_extra_header_lines(buffer, size, exclude);
+	extra = read_commit_extra_header_lines(buffer, size, exclude, extra);
 	unuse_commit_buffer(commit, buffer);
 	return extra;
 }
@@ -1311,7 +1309,7 @@ static inline int standard_header_field(const char *field, size_t len)
 		(len == 8 && !memcmp(field, "encoding ", 9)));
 }

-static int excluded_header_field(const char *field, size_t len, const char **exclude)
+static int excluded_header_field(const char *field, size_t len, char **exclude)
 {
 	if (!exclude)
 		return 0;
@@ -1326,11 +1324,11 @@ static int excluded_header_field(const char *field, size_t len, const char **exc
 	return 0;
 }

-static struct commit_extra_header *read_commit_extra_header_lines(
+struct commit_extra_header *read_commit_extra_header_lines(
 	const char *buffer, size_t size,
-	const char **exclude)
+	char **exclude, struct commit_extra_header *extra)
 {
-	struct commit_extra_header *extra = NULL, **tail = &extra, *it = NULL;
+	struct commit_extra_header **tail = extra ? &extra->next : &extra, *it = NULL;
 	const char *line, *next, *eof, *eob;
 	struct strbuf buf = STRBUF_INIT;

diff --git a/commit.h b/commit.h
index 9f189cb..f126d51 100644
--- a/commit.h
+++ b/commit.h
@@ -324,7 +324,8 @@ extern int commit_tree_extended(const char *msg, size_t msg_len,
 				const char *author, const char *sign_commit,
 				struct commit_extra_header *);

-extern struct commit_extra_header *read_commit_extra_headers(struct commit *, const char **);
+extern struct commit_extra_header *read_commit_extra_headers(struct commit *, char **);
+extern struct commit_extra_header *read_commit_extra_header_lines(const char *, size_t, char **, struct commit_extra_header *);

 extern void free_commit_extra_headers(struct commit_extra_header *extra);

--
2.0.5