[PATCH v2] scripts: create kernel configuration upgrade script

Elliott Mitchell ehem+openwrt at m5p.com
Tue Feb 6 17:16:41 PST 2024


Create a script for automating kernel version changes.  This
generates a pair of commits which cause history to remain attached to
all versioned configuration files.

Crucially this makes `git blame` work without needing
--find-copies-harder, which is too slow for routine use.  This also
updates *everything*, which greatly simplifies rebasing patches
which effect multiple devices.

Signed-off-by: Elliott Mitchell <ehem+openwrt at m5p.com>
---
v2:
Major tweaking.  No longer try to do `git merge --ff-only <hash>`,
but instead advise user to do so.

Add usage message.

Add statement about strategy.

Adjust commit messages.  Add advice to run `git bisect --skip` if
someone ends up on that commit.
---
 scripts/kernel_upgrade.pl | 261 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 261 insertions(+)
 create mode 100755 scripts/kernel_upgrade.pl

diff --git a/scripts/kernel_upgrade.pl b/scripts/kernel_upgrade.pl
new file mode 100755
index 0000000000..b9fe5882a3
--- /dev/null
+++ b/scripts/kernel_upgrade.pl
@@ -0,0 +1,261 @@
+#!/usr/bin/env perl
+#########################################################################
+# Copyright (C) 2024 Elliott Mitchell <ehem+openwrt at m5p.com>		#
+#									#
+# This is free software, licensed under the GNU General Public License	#
+# v3.  See /LICENSE for more information.				#
+#########################################################################
+
+use warnings;
+use strict;
+
+use feature 'state';
+
+use Digest::SHA qw(sha1_hex);
+
+#
+# Credit to the person who knew about this workable solution:
+# <https://lists.openwrt.org/pipermail/openwrt-devel/2023-October/041672.html>
+#
+# Which originated from:
+# <https://devblogs.microsoft.com/oldnewthing/20190919-00/?p=102904>
+#
+# Problem is copying a file in git causes the new file to be created
+# without any history.  Files can move around without losing their
+# history, but that only leaves the history on the new location.
+#
+# As such this can be solved with two commits.  The first commit moves
+# files from their old name to their new name.  The second merges the
+# original commit with the rename commit.  The merge commit then has
+# files in both locations with the full history.
+#
+#
+# Note, git handles discarded data by garbage collection.  When doing
+# development on this script, beware this script is an excellent
+# garbage generator.  Frequent use of `git gc` and `git prune` may be
+# needed.
+#
+
+
+sub gethead()
+{
+	open(my $fd, '-|', 'git', 'rev-parse', 'HEAD');
+	$_=<$fd>;
+	chop;
+	return $_;
+}
+
+sub getlist($$)
+{
+	my ($target, $from)=@_;
+	my $ret=[];
+
+	local $/="\0";
+	open(my $fd, '-| :raw :bytes', 'git', 'ls-tree', '-trz', '--full-name',
+'--name-only', 'HEAD', '--', $target)||die("failed to read git tree");
+
+	while(<$fd>) {
+		chop($_);
+		push(@$ret, substr($_, 0, -length($from)))
+if(substr($_, -length($from)) eq $from);
+	}
+
+	@$ret=sort({length($b)-length($a)} @$ret);
+
+	return $ret;
+}
+
+# start of interface to git fast-import
+my $gitpid;
+my $gitfds=[undef, undef];
+
+sub startgit()
+{
+	my $child=[];
+	(pipe($child->[0], $gitfds->[0])&&pipe($gitfds->[1], $child->[1])) ||
+die("pipe() failed");
+	binmode($gitfds->[0]);
+	binmode($gitfds->[1]);
+
+	$gitpid=fork();
+	if($gitpid==0) {
+		close($gitfds->[0]);
+		close($gitfds->[1]);
+
+		open(STDIN, '<&', $child->[0]);
+		close($child->[0]);
+
+		open(STDOUT, '>&', $child->[1]);
+		close($child->[1]);
+
+		exec('git', 'fast-import', '--done');
+		die('exec() of git failed');
+	} elsif(!$gitpid) {
+		die('fork() failed');
+	}
+	close($child->[0]);
+	close($child->[1]);
+	$gitfds->[0]->autoflush(1);
+
+}
+
+sub gitsend
+{
+	return print({$gitfds->[0]} @_);
+}
+
+sub gitrecv()
+{
+	return $_=readline(${$gitfds}[1]);
+}
+
+sub gitls($$)
+{
+	my ($commit, $name)=@_;
+	local $/="\n";
+
+	$commit.=' ' if($commit);
+	gitsend("ls $commit$name\n");
+	gitrecv();
+
+	die('git ls failed') unless(/^([0-8]+)\s+[a-z]+\s+([0-9a-z]+)\s+.+$/);
+
+	return [$1, $2];
+}
+
+sub gitcommit($$$)
+{
+	my ($dest, $message, $mark)=@_;
+	local $/="\n";
+	local $|=1;
+	state $author=undef;
+	unless($author) {
+		$author=['', ''];
+		open(my $user, '-|', 'git', 'config', '--get', 'user.name');
+		while(<$user>) {
+			chomp;
+			$author->[0].=$_;
+		}
+		$author->[0]=[split(/,/, [getpwuid($<)]->[6])]->[0]
+unless($author->[0]);
+
+		open(my $email, '-|', 'git', 'config', '--get', 'user.email');
+		while(<$email>) {
+			chomp;
+			$author->[1].=$_;
+		}
+		$author->[1]='anonymous at example.com' unless($author->[1]);
+
+		$author=$author->[0].' <'.$author->[1].'>';
+	}
+
+	$_=sha1_hex(time());
+	gitsend("commit $_\n");
+	gitsend("mark $mark\n");
+	gitsend("committer $author ".time()." +0000\n");
+
+	$_=length($message);
+	gitsend("data $_\n");
+	gitsend($message);
+	gitsend("from $dest\n");
+}
+
+sub gitdone()
+{
+	local $/="\n";
+	gitsend("done\n");
+	close($gitfds->[0]);
+	$gitfds->[0]=undef;
+
+	0 while(waitpid($gitpid, 0)!=$gitpid);
+	close($gitfds->[1]);
+	$gitfds->[1]=undef;
+
+	print(STDERR <<~"__GIT_STATUS__") if($?);
+	WARNING: git returned error exit status: $?
+
+	This likely means `git gc` needs to be run, but the return codes of
+	`git fast-import` are undocumented.
+
+	__GIT_STATUS__
+}
+# end of interface to git fast-import
+
+
+die(<<"__USAGE__") if(@ARGV!=2);
+Usage: $0 <old-version> <new-version>
+
+Copies all kernel configuration files and patches from the old version
+to the new version.  Git history is preserved on the copies by using a
+move/merge strategy.  Must be run while somewhere inside the git
+repository directory, but it does not matter where.
+__USAGE__
+
+my ($from, $to)=@ARGV;
+
+
+my $target='target/linux';
+
+my $start=gethead();
+
+my $list=getlist($target, $from);
+
+die("no files matching \"$from\" found") unless(@$list);
+
+
+startgit();
+
+gitcommit($start, <<"__TMP__", ':1');
+kernel: add configs and patches for $to
+
+This is a special tool-generated commit.
+
+Copy configuration and patches from $from to $to.
+
+If you see this commit during a `git bisect` session, the recommended
+course of action is to run `git bisect --skip`.
+__TMP__
+
+foreach my $name (@$list) {
+	my $new=gitls($start, "$name$from");
+	gitsend("M $new->[0] $new->[1] $name$to\n");
+	gitsend("D $name$from\n");
+}
+gitsend("\n");
+
+
+gitcommit(':1', <<"__TMP__", ':2');
+kernel: finish update from $from to $to
+
+This is a special tool-generated commit.
+
+Merge the add commit into HEAD to create all files with full history.
+__TMP__
+
+gitsend("merge $start\n");
+
+foreach my $name (@$list) {
+	my $new=gitls($start, "$name$from");
+	gitsend("M $new->[0] $new->[1] $name$from\n");
+}
+gitsend("\n");
+
+
+gitsend("get-mark :2\n");
+my $result=gitrecv();
+chomp($result);
+
+gitdone();
+
+print(<<"__END__");
+Result is commit $result.
+
+Depending on the setup of your development environment, you now likely
+want to run one of two commands:
+
+	`git merge --ff-only $result`
+Or:
+	`git branch linux-$to $result`
+__END__
+
+exit(0);
-- 
2.39.2




More information about the openwrt-devel mailing list