Skip to contentSkip to conversation

cvsimport move over to using git for each ref to read refs

PATCH In review Andy Whitcroft 3 participants 15 messages Jun 15, 2016

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 cvsimport move over to using git for each ref to read refs

cvsimport: move over to using git-for-each-ref to read refs

cvsimport opens all of the files in $GIT_DIR/refs/heads and reads out the sha1's in order to work out what time the last commit on that branch was made (in CVS) thus allowing incremental updates. However, this takes no account of hierachical refs naming producing the following error for each directory in $GIT_DIR/refs:

Use of uninitialized value in chomp at /usr/bin/git-cvsimport line 503. Use of uninitialized value in concatenation (.) or string at
/usr/bin/git-cvsimport line 505.
usage: git-cat-file [-t|-s|-e|-p|<type>] <sha1>

Take advantage of the new packed refs work to use the new for-each-ref iterator to get this information. Use the format specifier to ensure we are neutral to changes in default.

[Junio: although although for-each-ref offers a --perl quoting mode this patch does not use it as it seems only to make parsing the output harder in perl. If there is a neat trick for handling this 'perl' form please educate me. Here, rely on sha1's to contain no spaces.]

---

git-cvsimport.perl +9 -8
index e5a00a1..1a62afa 100755
@@ -495,13 +495,14 @@ unless(-d $git_dir) {
 	$tip_at_start = `git-rev-parse --verify HEAD`;
 
 	# Get the last import timestamps
-	opendir(D,"$git_dir/refs/heads");
-	while(defined(my $head = readdir(D))) {
-		next if $head =~ /^\./;
-		open(F,"$git_dir/refs/heads/$head")
-			or die "Bad head branch: $head: $!\n";
-		chomp(my $ftag = <F>);
-		close(F);
+	open(H, "git-for-each-ref --format='%(objectname) %(refname)'|") or
+		die "Cannot run git-for-each-ref: $!\n";
+	while(defined(my $entry = <H>)) {
+		chomp($entry);
+		my ($ftag, $name) = split(' ', $entry, 2);
+		next if ($name !~ m@^refs/heads/(.*)$@);
+		my ($head) = ($1);
+
 		open(F,"git-cat-file commit $ftag |");
 		while(<F>) {
 			next unless /^author\s.*\s(\d+)\s[-+]\d{4}$/;
@@ -510,7 +511,7 @@ unless(-d $git_dir) {
 		}
 		close(F);
 	}
-	closedir(D);
+	close(H);
 }
 
 -d $git_dir
#1 lore raw
Quoted 1 line · Andy Whitcroft

+       open(H, "git-for-each-ref --format='%(objectname) %(refname)'|") or

By the way, this is equivalent to using "git-show-ref" introduced by Linus. But if you want commit timestamp

Quoted 3 lines

cvsimport opens all of the files in $GIT_DIR/refs/heads and reads out the sha1's in order to work out what time the last commit on that branch was made (in CVS) thus allowing incremental updates.

you can use it in --format as well.

Signature · 3 lines
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
#2 lore raw
Quoted 4 lines · Jakub Narebski
Quoted 1 line · Andy Whitcroft

+ open(H, "git-for-each-ref --format='%(objectname) %(refname)'|") or

Quoted 8 lines

By the way, this is equivalent to using "git-show-ref" introduced by Linus. But if you want commit timestamp

Quoted 3 lines

cvsimport opens all of the files in $GIT_DIR/refs/heads and reads out the sha1's in order to work out what time the last commit on that branch was made (in CVS) thus allowing incremental updates.

you can use it in --format as well.

Unfortuantly, for-each-ref only offers us the textual version of this information not the numeric offset from the epoch which is what cvsimport is after.

I guess we could teach for-each-ref to output this as well? Perhaps something like authorstamp?

-apw

#3 lore raw

PATCH 1/2 for each ref add a raw timestamp field type

for-each-ref: add a raw timestamp field type

cvsimport is interested in the raw time stamps (in seconds since the epoch) to do its time comparisons. Export the raw timestamp under {author,committer,tagger}stamp.

---

builtin-for-each-ref.c +19 -4
index 698618b..9d6c4f0 100644
@@ -51,14 +51,17 @@ static struct {
 	{ "authorname" },
 	{ "authoremail" },
 	{ "authordate", FIELD_TIME },
+	{ "authorstamp", FIELD_TIME },
 	{ "committer" },
 	{ "committername" },
 	{ "committeremail" },
 	{ "committerdate", FIELD_TIME },
+	{ "committerstamp", FIELD_TIME },
 	{ "tagger" },
 	{ "taggername" },
 	{ "taggeremail" },
 	{ "taggerdate", FIELD_TIME },
+	{ "taggerstamp", FIELD_TIME },
 	{ "subject" },
 	{ "body" },
 	{ "contents" },
@@ -344,9 +347,10 @@ static char *copy_email(const char *buf)
 	return line;
 }
 
-static void grab_date(const char *buf, struct atom_value *v)
+static void grab_date(const char *buf, struct atom_value *v, int raw)
 {
 	const char *eoemail = strstr(buf, "> ");
+	const char *eol = strchr(buf, '\n');
 	char *zone;
 	unsigned long timestamp;
 	long tz;
@@ -359,7 +363,15 @@ static void grab_date(const char *buf, s
 	tz = strtol(zone, NULL, 10);
 	if ((tz == LONG_MIN || tz == LONG_MAX) && errno == ERANGE)
 		goto bad;
-	v->s = xstrdup(show_date(timestamp, tz, 0));
+	if (raw) {
+		int len = (eol - eoemail - 2);
+		char *stamp = xmalloc(len + 1);
+
+		memcpy(stamp, eoemail + 2, len);
+		stamp[len] = 0;
+		v->s = stamp;
+	} else
+		v->s = xstrdup(show_date(timestamp, tz, 0));
 	v->ul = timestamp;
 	return;
  bad:
@@ -386,7 +398,8 @@ static void grab_person(const char *who,
 		if (name[wholen] != 0 &&
 		    strcmp(name + wholen, "name") &&
 		    strcmp(name + wholen, "email") &&
-		    strcmp(name + wholen, "date"))
+		    strcmp(name + wholen, "date") &&
+		    strcmp(name + wholen, "stamp"))
 			continue;
 		if (!wholine)
 			wholine = find_wholine(who, wholen, buf, sz);
@@ -399,7 +412,9 @@ static void grab_person(const char *who,
 		else if (!strcmp(name + wholen, "email"))
 			v->s = copy_email(wholine);
 		else if (!strcmp(name + wholen, "date"))
-			grab_date(wholine, v);
+			grab_date(wholine, v, 0);
+		else if (!strcmp(name + wholen, "stamp"))
+			grab_date(wholine, v, 1);
 	}
 }
 
#4 lore raw

PATCH 2/2 cvsimport move over to using git for each ref to read refs V2

In reply to Andy Whitcroft

cvsimport: move over to using git-for-each-ref to read refs V2

cvsimport opens all of the files in $GIT_DIR/refs/heads and reads out the sha1's in order to work out what time the last commit on that branch was made (in CVS) thus allowing incremental updates. However, this takes no account of hierachical refs naming producing the following error for each directory in $GIT_DIR/refs:

Use of uninitialized value in chomp at /usr/bin/git-cvsimport line 503. Use of uninitialized value in concatenation (.) or string at
/usr/bin/git-cvsimport line 505.
usage: git-cat-file [-t|-s|-e|-p|<type>] <sha1>

Take advantage of the new packed refs work to use the new for-each-ref iterator to get this information. Use the format specifier to ensure we are neutral to changes in default.

[Junio: although although for-each-ref offers a --perl quoting mode this patch does not use it as it seems only to make parsing the output harder in perl. If there is a neat trick for handling this 'perl' form please educate me. Here, rely on sha1's and refnames to contain no spaces.]

---

git-cvsimport.perl +10 -15
index e5a00a1..5b13c23 100755
@@ -495,22 +495,17 @@ unless(-d $git_dir) {
 	$tip_at_start = `git-rev-parse --verify HEAD`;
 
 	# Get the last import timestamps
-	opendir(D,"$git_dir/refs/heads");
-	while(defined(my $head = readdir(D))) {
-		next if $head =~ /^\./;
-		open(F,"$git_dir/refs/heads/$head")
-			or die "Bad head branch: $head: $!\n";
-		chomp(my $ftag = <F>);
-		close(F);
-		open(F,"git-cat-file commit $ftag |");
-		while(<F>) {
-			next unless /^author\s.*\s(\d+)\s[-+]\d{4}$/;
-			$branch_date{$head} = $1;
-			last;
-		}
-		close(F);
+	open(H, "git-for-each-ref --format='%(objectname) %(refname) %(authorstamp)'|") or
+		die "Cannot run git-for-each-ref: $!\n";
+	while(defined(my $entry = <H>)) {
+		chomp($entry);
+		my ($ftag, $name, $stamp, $zone) = split(/ /, $entry, 4);
+		next if ($name !~ m@^refs/heads/(.*)$@);
+		my ($head) = ($1);
+
+		$branch_date{$head} = $stamp;
 	}
-	closedir(D);
+	close(H);
 }
 
 -d $git_dir
#5 lore raw
In reply to Andy Whitcroft
Quoted 12 lines · Andy Whitcroft
Quoted 11 lines · Jakub Narebski
Quoted 1 line · Andy Whitcroft

+ open(H, "git-for-each-ref --format='%(objectname) %(refname)'|") or

By the way, this is equivalent to using "git-show-ref" introduced by Linus. But if you want commit timestamp

Quoted 3 lines

cvsimport opens all of the files in $GIT_DIR/refs/heads and reads out the sha1's in order to work out what time the last commit on that branch was made (in CVS) thus allowing incremental updates.

Quoted 8 lines
Quoted 1 line

you can use it in --format as well.

Unfortuantly, for-each-ref only offers us the textual version of this information not the numeric offset from the epoch which is what cvsimport is after.

I guess we could teach for-each-ref to output this as well? Perhaps something like authorstamp?

Or authorepoch.

I thought about following rpm idea of format modifiers, like authorepoch:date for strftime(3) "%c" format or commiterepoch:day for strftime(3) "%a %b %d %Y" format.

Feel free to provide patches ;-)

Signature · 2 lines
Jakub Narebski
Poland
#6 lore raw
In reply to Andy Whitcroft
Quoted 2 lines · Andy Whitcroft

I guess we could teach for-each-ref to output this as well? Perhaps something like authorstamp?

I think you can work with "author" or "committer" to grab the whole raw line.

About the quoting and parsing, language specific quoting mode is meant for git-for-each-ref to produce a string that can be eval'ed in the host language. Think of the command as a tool to write a short program for you.

The original is like this:

	# Get the last import timestamps
	opendir(D,"$git_dir/refs/heads");
	while(defined(my $head = readdir(D))) {
		next if $head =~ /^\./;
		open(F,"$git_dir/refs/heads/$head")
			or die "Bad head branch: $head: $!\n";
		chomp(my $ftag = <F>);
		close(F);
		open(F,"git-cat-file commit $ftag |");
		while(<F>) {
			next unless /^author\s.*\s(\d+)\s[-+]\d{4}$/;
			$branch_date{$head} = $1;
			last;
		}
		close(F);
	}
	closedir(D);

The purpose of the loop is to grab all branch heads, and grab author timestamp for all of them, _and_ stash that in %branch_date hash indexed by head name. You would want to have something like this executed for each branch:

	$refname = %(refname);
	$authorline = %(author);
	$authorline =~ /^author\s.*\s(\d+)\s[-+]\d{4}$/;
        $branch_date{$refname} = $1;

So, you have the tool to write such a program for you, by doing something like this:

	my $template = '
                $refname = %(refname);
                $authorline = %(author);
                $authorline =~ /^author\s.*\s(\d+)\s[-+]\d{4}$/;
                $branch_date{$refname} = $1;
	';
        my @cmd = ('git-for-each-ref', '--perl', "--format=$template");

	open I, '-|', @cmd, 'refs/heads';
        my $script = join('',<I>);
        close I;

	my ($refname, $authorline);
        eval "$script";

The language specific quoting flag --perl affects how %() are interpolated into the generated program text as literals. That's why there is no quote around %(refname) or %(author) in the example above when defining the $template.

#7 lore raw
Quoted 5 lines · Junio C Hamano
Quoted 2 lines · Andy Whitcroft

I guess we could teach for-each-ref to output this as well? Perhaps something like authorstamp?

Quoted 7 lines

I think you can work with "author" or "committer" to grab the whole raw line.

About the quoting and parsing, language specific quoting mode is meant for git-for-each-ref to produce a string that can be eval'ed in the host language. Think of the command as a tool to write a short program for you.

Thanks for the education. Very simple, and very powerful. I knew there was a reason for it out there. Will respin a V3 patch in a bit.

-apw

#8 lore raw

PATCH 1/1 cvsimport move over to using git for each ref to read refs V3

cvsimport: move over to using git-for-each-ref to read refs V3

cvsimport opens all of the files in $GIT_DIR/refs/heads and reads out the sha1's in order to work out what time the last commit on that branch was made (in CVS) thus allowing incremental updates. However, this takes no account of hierachical refs naming producing the following error for each directory in $GIT_DIR/refs:

Use of uninitialized value in chomp at /usr/bin/git-cvsimport line 503. Use of uninitialized value in concatenation (.) or string at
/usr/bin/git-cvsimport line 505.
usage: git-cat-file [-t|-s|-e|-p|<type>] <sha1>

Take advantage of the new packed refs work to use the new for-each-ref iterator to get this information.

---

git-cvsimport.perl +12 -15
index e5a00a1..92d14c3 100755
@@ -495,22 +495,19 @@ unless(-d $git_dir) {
 	$tip_at_start = `git-rev-parse --verify HEAD`;
 
 	# Get the last import timestamps
-	opendir(D,"$git_dir/refs/heads");
-	while(defined(my $head = readdir(D))) {
-		next if $head =~ /^\./;
-		open(F,"$git_dir/refs/heads/$head")
-			or die "Bad head branch: $head: $!\n";
-		chomp(my $ftag = <F>);
-		close(F);
-		open(F,"git-cat-file commit $ftag |");
-		while(<F>) {
-			next unless /^author\s.*\s(\d+)\s[-+]\d{4}$/;
-			$branch_date{$head} = $1;
-			last;
-		}
-		close(F);
+	my $fmt = '($ref, $author) = (%(refname), %(author));';
+	open(H, "git-for-each-ref --perl --format='$fmt'|") or
+		die "Cannot run git-for-each-ref: $!\n";
+	while(defined(my $entry = <H>)) {
+		my ($ref, $author);
+		eval($entry) || die "cannot eval refs list: $@";
+
+		next if ($ref !~ m@^refs/heads/(.*)$@);
+		my ($head) = ($1);
+		$author =~ /^.*\s(\d+)\s[-+]\d{4}$/;
+		$branch_date{$head} = $1;
 	}
-	closedir(D);
+	close(H);
 }
 
 -d $git_dir
#9 lore raw

Re: [PATCH] cvsimport move over to using git for each ref to read refs V3

Quoted 5 lines · Andy Whitcroft
git-cvsimport.perl +5 -0
+	my $fmt = '($ref, $author) = (%(refname), %(author));';
+	open(H, "git-for-each-ref --perl --format='$fmt'|") or
+		die "Cannot run git-for-each-ref: $!\n";
+	while(defined(my $entry = <H>)) {
+		my ($ref, $author);

git-cvsimport.perl unless(-d $git_dir) { · line 511

Quoted 10 lines
git-cvsimport.perl +7 -1
+		eval($entry) || die "cannot eval refs list: $@";
+
+		next if ($ref !~ m@^refs/heads/(.*)$@);
+		my ($head) = ($1);
+		$author =~ /^.*\s(\d+)\s[-+]\d{4}$/;
+		$branch_date{$head} = $1;
 	}
-	closedir(D);
+	close(H);
 }

for-each-ref let's you limit the refs by leading path, so I do not think "next if" inside the loop is needed if you say

	for-each-ref --perl --format=$fmt refs/heads

Any reason you did not like my version that allows you to get rid of the while() loop altogether? (hint: replace eval in my example with "print" and see what you are evaling)

#10 lore raw

Re: [PATCH] cvsimport move over to using git for each ref to read refs V3

Quoted 21 lines · Junio C Hamano
Quoted 5 lines · Andy Whitcroft
git-cvsimport.perl +5 -0
+	my $fmt = '($ref, $author) = (%(refname), %(author));';
+	open(H, "git-for-each-ref --perl --format='$fmt'|") or
+		die "Cannot run git-for-each-ref: $!\n";
+	while(defined(my $entry = <H>)) {
+		my ($ref, $author);

git-cvsimport.perl unless(-d $git_dir) { · line 511

Quoted 10 lines
git-cvsimport.perl +7 -1
+		eval($entry) || die "cannot eval refs list: $@";
+
+		next if ($ref !~ m@^refs/heads/(.*)$@);
+		my ($head) = ($1);
+		$author =~ /^.*\s(\d+)\s[-+]\d{4}$/;
+		$branch_date{$head} = $1;
 	}
-	closedir(D);
+	close(H);
 }

for-each-ref let's you limit the refs by leading path, so I do not think "next if" inside the loop is needed if you say

Quoted 1 line
	for-each-ref --perl --format=$fmt refs/heads

Stupid boy :)

Quoted 4 lines

Any reason you did not like my version that allows you to get rid of the while() loop altogether? (hint: replace eval in my example with "print" and see what you are evaling)

To my mind we avoid the 'mozilla repo' issue of 1000's of heads busting some string length limit, or requiring some _vast_ string to hold it (as perl is likely to cope). Given we are sifting a small percentage of the data out of it.

Let me know which way you want it and we'll go that way :).

-apw

#11 lore raw

Re: [PATCH] cvsimport move over to using git for each ref to read refs V3

Quoted 9 lines · Andy Whitcroft
Quoted 5 lines · Junio C Hamano

for-each-ref let's you limit the refs by leading path, so I do not think "next if" inside the loop is needed if you say

	for-each-ref --perl --format=$fmt refs/heads

Stupid boy :)

Quoted 1 line
Quoted 8 lines
Quoted 3 lines

Any reason you did not like my version that allows you to get rid of the while() loop altogether? (hint: replace eval in my example with "print" and see what you are evaling)

To my mind we avoid the 'mozilla repo' issue of 1000's of heads busting some string length limit, or requiring some _vast_ string to hold it (as perl is likely to cope). Given we are sifting a small percentage of the data out of it.

Quite valid reasoning.

Will apply V3 with refs/heads filtering added; no need to resend.

#12 lore raw
In reply to Andy Whitcroft
Quoted 7 lines · Andy Whitcroft
Quoted 4 lines

About the quoting and parsing, language specific quoting mode is meant for git-for-each-ref to produce a string that can be eval'ed in the host language. Think of the command as a tool to write a short program for you.

Thanks for the education. Very simple, and very powerful. I knew there was a reason for it out there. Will respin a V3 patch in a bit.

This probably showed that my initial description and example for the feature found in Documentation/git-for-each-ref was lacking.

I would appreciate a separate patch to enhance it if you are so inclined.

#13 lore raw
Quoted 6 lines · Junio C Hamano
Quoted 4 lines · Andy Whitcroft
Quoted 4 lines

About the quoting and parsing, language specific quoting mode is meant for git-for-each-ref to produce a string that can be eval'ed in the host language. Think of the command as a tool to write a short program for you.

Quoted 8 lines
Quoted 2 lines

Thanks for the education. Very simple, and very powerful. I knew there was a reason for it out there. Will respin a V3 patch in a bit.

This probably showed that my initial description and example for the feature found in Documentation/git-for-each-ref was lacking.

I would appreciate a separate patch to enhance it if you are so inclined.

Actually the description in that manual page is pretty good. But I thought it was undocumented as the standard install does not install the manual pages and I was getting the ones from my stable install to fool me into thinking they were installed. New developer error, doh.

That said I've taken the liberty of updating and clarifying the language to make it very obvious from the outset that the language specific output formats are for generating eval'able snippets and added a simple example of it to complement your complex one.

Patch to follow.

-apw

#14 lore raw
Quoted 6 lines · Andy Whitcroft

... I've taken the liberty of updating and clarifying the language to make it very obvious from the outset that the language specific output formats are for generating eval'able snippets and added a simple example of it to complement your complex one.

Patch to follow.

Thanks.