[PATCH] scripts: create kernel configuration upgrade script
Elliott Mitchell
ehem+openwrt at m5p.com
Sun Mar 3 15:24:50 PST 2024
Date: Tue, 6 Feb 2024 17:16:41 -0800
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.
Credit to Christian Marangi who knew of the technique:
<https://lists.openwrt.org/pipermail/openwrt-devel/2023-October/041672.html>
Signed-off-by: Elliott Mitchell <ehem+openwrt at m5p.com>
---
v3:
Dust off knowledge of PerlOO. Confine the fast-importer interface
to an object.
Better layer the lowest I/O layer. If fast-import grows a \0 command
separation mode, we should be mostly ready (issue will be the commit
message).
Switch to SPDX. Try to match what the other scripts have.
I was kind of hoping for more review activity, the near-silence is
almost deafening. Using a script to handle this job seems best. I
feel what this script produces is rather easier for most developers
to handle.
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 | 280 ++++++++++++++++++++++++++++++++++++++
1 file changed, 280 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..c2e5fb6078
--- /dev/null
+++ b/scripts/kernel_upgrade.pl
@@ -0,0 +1,280 @@
+#!/usr/bin/env perl
+#########################################################################
+# SPDX-License-Identifier: GPL-3.0-or-later #
+# #
+# Copyright (C) 2024 Elliott Mitchell <ehem+openwrt at m5p.com> #
+#########################################################################
+
+use warnings;
+use strict;
+
+#
+# I'm not certain the technique originated here, but this comes 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
+package GitImporter;
+
+# git fast-import's protocol uses *linefeeds*
+local $/="\n";
+
+sub new()
+{
+ my $class=shift;
+ my $self={};
+ my @child;
+ (pipe($child[0], $self->{out})&&pipe($self->{in}, $child[1])) ||
+die("pipe() failed");
+ binmode($self->{out});
+ binmode($self->{in});
+
+ $self->{pid}=fork();
+ if($self->{pid}==0) {
+ close($self->{out});
+ close($self->{in});
+
+ 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(!$self->{pid}) {
+ die('fork() failed');
+ }
+ close($child[0]);
+ close($child[1]);
+ $self->{out}->autoflush(1);
+
+ return bless($self, $class);
+}
+
+sub send($)
+{
+ my $self=shift;
+ my ($data)=@_;
+ return print({$self->{out}} $data);
+}
+
+sub putcmd($)
+{
+ my $self=shift;
+ my ($data)=@_;
+ return $self->send("$data\n");
+}
+
+sub recv()
+{
+ my $self=shift;
+ return $_=readline($self->{in});
+}
+
+sub getres()
+{
+ my $self=shift;
+ $_=$self->recv();
+ chomp;
+ return $_;
+}
+
+sub ls($$)
+{
+ my $self=shift;
+ my ($commit, $name)=@_;
+
+ $commit.=' ' if($commit);
+ $self->putcmd("ls $commit$name");
+ $self->getres();
+
+ die('git ls failed') unless(/^([0-8]+)\s+[a-z]+\s+([0-9a-z]+)\s+.+$/);
+
+ return [$1, $2];
+}
+
+sub commit($$$)
+{
+ my $self=shift;
+ my ($dest, $message, $mark)=@_;
+
+ use feature 'state';
+ use Digest::SHA qw(sha1_hex);
+
+ 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());
+ $self->putcmd("commit $_");
+ $self->putcmd("mark $mark");
+ $self->putcmd("committer $author ".time()." +0000");
+
+ $_=length($message);
+ $self->putcmd("data $_");
+ $self->send($message);
+ $self->putcmd("from $dest");
+}
+
+sub DESTROY()
+{
+ my $self=shift;
+
+ $self->putcmd('done');
+ close($self->{out});
+ delete $self->{out};
+
+ 0 while(waitpid($self->{pid}, 0)!=$self->{pid});
+ delete $self->{pid};
+ close($self->{in});
+ delete $self->{in};
+
+ 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);
+
+
+my $git=GitImporter->new();
+
+$git->commit($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=$git->ls($start, "$name$from");
+ $git->putcmd("M $new->[0] $new->[1] $name$to");
+ $git->putcmd("D $name$from");
+}
+$git->putcmd('');
+
+
+$git->commit(':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__
+
+$git->putcmd("merge $start");
+
+foreach my $name (@$list) {
+ my $new=$git->ls($start, "$name$from");
+ $git->putcmd("M $new->[0] $new->[1] $name$from");
+}
+$git->putcmd('');
+
+
+$git->putcmd('get-mark :2');
+my $result=$git->getres();
+
+undef($git);
+
+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