--- embedaddon/rsync/packaging/branch-from-patch 2013/10/14 07:51:14 1.1.1.1 +++ embedaddon/rsync/packaging/branch-from-patch 2021/03/17 00:32:36 1.1.1.2 @@ -1,180 +1,174 @@ -#!/usr/bin/perl +#!/usr/bin/env -S python3 -B -use strict; -use warnings; -use Getopt::Long; +# This script turns one or more diff files in the patches dir (which is +# expected to be a checkout of the rsync-patches git repo) into a branch +# in the main rsync git checkout. This allows the applied patch to be +# merged with the latest rsync changes and tested. To update the diff +# with the resulting changes, see the patch-update script. -&Getopt::Long::Configure('bundling'); -&usage if !&GetOptions( - 'branch|b=s' => \( my $master_branch = 'master' ), - 'skip-check' => \( my $skip_branch_check ), - 'delete' => \( my $delete_local_branches ), - 'help|h' => \( my $help_opt ), -); -&usage if $help_opt; +import os, sys, re, argparse, glob -require 'packaging/git-status.pl'; -check_git_state($master_branch, !$skip_branch_check, 1); +sys.path = ['packaging'] + sys.path -my %local_branch; -open PIPE, '-|', 'git branch -l' or die "Unable to fork: $!\n"; -while () { - if (m# patch/\Q$master_branch\E/(.*)#o) { - $local_branch{$1} = 1; - } -} -close PIPE; +from pkglib import * -if ($delete_local_branches) { - foreach my $name (sort keys %local_branch) { - my $branch = "patch/$master_branch/$name"; - system 'git', 'branch', '-D', $branch and exit 1; - } - %local_branch = ( ); -} +def main(): + global created, info, local_branch -my @patch_list; -foreach (@ARGV) { - if (!-f $_) { - die "File not found: $_\n"; - } - die "Filename is not a .diff file: $_\n" unless /\.diff$/; - push @patch_list, $_; -} + cur_branch, args.base_branch = check_git_state(args.base_branch, not args.skip_check, args.patches_dir) -exit unless @patch_list; + local_branch = get_patch_branches(args.base_branch) -my(%scanned, %created, %info); + if args.delete_local_branches: + for name in sorted(local_branch): + branch = f"patch/{args.base_branch}/{name}" + cmd_chk(['git', 'branch', '-D', branch]) + local_branch = set() -foreach my $patch (@patch_list) { - my($where, $name) = $patch =~ m{^(.*?)([^/]+)\.diff$}; - next if $scanned{$name}++; + if args.add_missing: + for fn in sorted(glob.glob(f"{args.patches_dir}/*.diff")): + name = re.sub(r'\.diff$', '', re.sub(r'.+/', '', fn)) + if name not in local_branch and fn not in args.patch_files: + args.patch_files.append(fn) - open IN, '<', $patch or die "Unable to open $patch: $!\n"; + if not args.patch_files: + return - my $info = ''; - my $commit; - while () { - if (m#^based-on: (\S+)#) { - $commit = $1; - last; - } - last if m#^index .*\.\..* \d#; - last if m#^diff --git #; - last if m#^--- (old|a)/#; - $info .= $_; - } - close IN; + for fn in args.patch_files: + if not fn.endswith('.diff'): + die(f"Filename is not a .diff file: {fn}") + if not os.path.isfile(fn): + die(f"File not found: {fn}") - $info =~ s/\s+\Z/\n/; + scanned = set() + info = { } - my $parent = $master_branch; - my @patches = $info =~ m#patch -p1 .*?)(?P[^/]+)\.diff$', fn) + patch = argparse.Namespace(**m.groupdict()) + if patch.name in scanned: + continue + patch.fn = fn - $info{$name} = [ $parent, $info, $commit ]; -} + lines = [ ] + commit_hash = None + with open(patch.fn, 'r', encoding='utf-8') as fh: + for line in fh: + m = re.match(r'^based-on: (\S+)', line) + if m: + commit_hash = m[1] + break + if (re.match(r'^index .*\.\..* \d', line) + or re.match(r'^diff --git ', line) + or re.match(r'^--- (old|a)/', line)): + break + lines.append(re.sub(r'\s*\Z', "\n", line, 1)) + info_txt = ''.join(lines).strip() + "\n" + lines = None -foreach my $patch (@patch_list) { - create_branch($patch); -} + parent = args.base_branch + patches = re.findall(r'patch -p1 <%s/(\S+)\.diff' % args.patches_dir, info_txt) + if patches: + last = patches.pop() + if last != patch.name: + warn(f"No identity patch line in {patch.fn}") + patches.append(last) + if patches: + parent = patches.pop() + if parent not in scanned: + diff_fn = patch.dir + parent + '.diff' + if not os.path.isfile(diff_fn): + die(f"Failed to find parent of {patch.fn}: {parent}") + # Add parent to args.patch_files so that we will look for the + # parent's parent. Any duplicates will be ignored. + args.patch_files.append(diff_fn) + else: + warn(f"No patch lines found in {patch.fn}") -system 'git', 'checkout', $master_branch and exit 1; + info[patch.name] = [ parent, info_txt, commit_hash ] -exit; + patch_list.append(patch) -sub create_branch -{ - my($patch) = @_; - my($where, $name) = $patch =~ m{^(.*?)([^/]+)\.diff$}; + created = set() + for patch in patch_list: + create_branch(patch) - return if $created{$name}++; + cmd_chk(['git', 'checkout', args.base_branch]) - my $ref = $info{$name}; - my($parent, $info, $commit) = @$ref; - my $parent_branch; - if ($parent eq $master_branch) { - $parent_branch = $master_branch; - $parent_branch = $commit if defined $commit; - } else { - create_branch("$where/$parent.diff"); - $parent_branch = "patch/$master_branch/$parent"; - } +def create_branch(patch): + if patch.name in created: + return + created.add(patch.name) - my $branch = "patch/$master_branch/$name"; - print "\n", '=' x 64, "\nProcessing $branch ($parent_branch)\n"; + parent, info_txt, commit_hash = info[patch.name] + parent = argparse.Namespace(dir=patch.dir, name=parent, fn=patch.dir + parent + '.diff') - if ($local_branch{$name}) { - system 'git', 'branch', '-D', $branch and exit 1; - } + if parent.name == args.base_branch: + parent_branch = commit_hash if commit_hash else args.base_branch + else: + create_branch(parent) + parent_branch = '/'.join(['patch', args.base_branch, parent.name]) - system 'git', 'checkout', '-b', $branch, $parent_branch and exit 1; + branch = '/'.join(['patch', args.base_branch, patch.name]) + print("\n" + '=' * 64) + print(f"Processing {branch} ({parent_branch})") - open OUT, '>', "PATCH.$name" or die $!; - print OUT $info; - close OUT; - system 'git', 'add', "PATCH.$name" and exit 1; + if patch.name in local_branch: + cmd_chk(['git', 'branch', '-D', branch]) - open IN, '<', $patch or die "Unable to open $patch: $!\n"; - $_ = join('', ); - close IN; + cmd_chk(['git', 'checkout', '-b', branch, parent_branch]) - open PIPE, '|-', 'patch -p1' or die $!; - print PIPE $_; - close PIPE; + info_fn = 'PATCH.' + patch.name + with open(info_fn, 'w', encoding='utf-8') as fh: + fh.write(info_txt) + cmd_chk(['git', 'add', info_fn]) - system 'rm -f *.orig */*.orig'; + with open(patch.fn, 'r', encoding='utf-8') as fh: + patch_txt = fh.read() - while (m#\nnew file mode (\d+)\s+--- /dev/null\s+\Q+++\E b/(.*)#g) { - chmod oct($1), $2; - system 'git', 'add', $2; - } + cmd_run('patch -p1'.split(), input=patch_txt) - while (1) { - system 'git status'; - print 'Press Enter to commit, Ctrl-C to abort, or type a wild-name to add a new file: '; - $_ = ; - last if /^$/; - chomp; - system "git add $_"; - } + for fn in glob.glob('*.orig') + glob.glob('*/*.orig'): + os.unlink(fn) - while (system 'git', 'commit', '-a', '-m', "Creating branch from $name.diff.") { - exit 1 if system '/bin/zsh'; - } -} + pos = 0 + new_file_re = re.compile(r'\nnew file mode (?P\d+)\s+--- /dev/null\s+\+\+\+ b/(?P.+)') + while True: + m = new_file_re.search(patch_txt, pos) + if not m: + break + os.chmod(m['fn'], int(m['mode'], 8)) + cmd_chk(['git', 'add', m['fn']]) + pos = m.end() -sub usage -{ - die <