Diff for /embedaddon/rsync/packaging/branch-from-patch between versions 1.1.1.1 and 1.1.1.2

version 1.1.1.1, 2013/10/14 07:51:14 version 1.1.1.2, 2021/03/17 00:32:36
Line 1 Line 1
#!/usr/bin/perl#!/usr/bin/env -S python3 -B
   
use strict;# This script turns one or more diff files in the patches dir (which is
use warnings;# expected to be a checkout of the rsync-patches git repo) into a branch
use Getopt::Long;# 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');import os, sys, re, argparse, glob
&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; 
   
require 'packaging/git-status.pl';sys.path = ['packaging'] + sys.path
check_git_state($master_branch, !$skip_branch_check, 1); 
   
my %local_branch;from pkglib import *
open PIPE, '-|', 'git branch -l' or die "Unable to fork: $!\n"; 
while (<PIPE>) { 
    if (m# patch/\Q$master_branch\E/(.*)#o) { 
        $local_branch{$1} = 1; 
    } 
} 
close PIPE; 
   
if ($delete_local_branches) {def main():
    foreach my $name (sort keys %local_branch) {    global created, info, local_branch
        my $branch = "patch/$master_branch/$name"; 
        system 'git', 'branch', '-D', $branch and exit 1; 
    } 
    %local_branch = ( ); 
} 
   
my @patch_list;    cur_branch, args.base_branch = check_git_state(args.base_branch, not args.skip_check, args.patches_dir)
foreach (@ARGV) { 
    if (!-f $_) { 
        die "File not found: $_\n"; 
    } 
    die "Filename is not a .diff file: $_\n" unless /\.diff$/; 
    push @patch_list, $_; 
} 
   
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) {    if args.add_missing:
    my($where, $name) = $patch =~ m{^(.*?)([^/]+)\.diff$};        for fn in sorted(glob.glob(f"{args.patches_dir}/*.diff")):
    next if $scanned{$name}++;            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 = '';    for fn in args.patch_files:
    my $commit;        if not fn.endswith('.diff'):
    while (<IN>) {            die(f"Filename is not a .diff file: {fn}")
        if (m#^based-on: (\S+)#) {        if not os.path.isfile(fn):
            $commit = $1;            die(f"File not found: {fn}")
            last; 
        } 
        last if m#^index .*\.\..* \d#; 
        last if m#^diff --git #; 
        last if m#^--- (old|a)/#; 
        $info .= $_; 
    } 
    close IN; 
   
    $info =~ s/\s+\Z/\n/;    scanned = set()
     info = { }
   
    my $parent = $master_branch;    patch_list = [ ]
    my @patches = $info =~ m#patch -p1 <patches/(\S+)\.diff#g;    for fn in args.patch_files:
    if (@patches) {        m = re.match(r'^(?P<dir>.*?)(?P<name>[^/]+)\.diff$', fn)
        if ($patches[-1] eq $name) {        patch = argparse.Namespace(**m.groupdict())
            pop @patches;        if patch.name in scanned:
        } else {            continue
            warn "No identity patch line in $patch\n";        patch.fn = fn
        } 
        if (@patches) { 
            $parent = pop @patches; 
            if (!$scanned{$parent}) { 
                unless (-f "$where$parent.diff") { 
                    die "Unknown parent of $patch: $parent\n"; 
                } 
                # Add parent to @patch_list so that we will look for the 
                # parent's parent.  Any duplicates will just be ignored. 
                push @patch_list, "$where$parent.diff"; 
            } 
        } 
    } else { 
        warn "No patch lines found in $patch\n"; 
    } 
   
    $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) {        parent = args.base_branch
    create_branch($patch);        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    created = set()
{    for patch in patch_list:
    my($patch) = @_;        create_branch(patch)
    my($where, $name) = $patch =~ m{^(.*?)([^/]+)\.diff$}; 
   
    return if $created{$name}++;    cmd_chk(['git', 'checkout', args.base_branch])
   
     my $ref = $info{$name};  
     my($parent, $info, $commit) = @$ref;  
   
    my $parent_branch;def create_branch(patch):
    if ($parent eq $master_branch) {    if patch.name in created:
        $parent_branch = $master_branch;        return
        $parent_branch = $commit if defined $commit;    created.add(patch.name)
    } else { 
        create_branch("$where/$parent.diff"); 
        $parent_branch = "patch/$master_branch/$parent"; 
    } 
   
    my $branch = "patch/$master_branch/$name";    parent, info_txt, commit_hash = info[patch.name]
    print "\n", '=' x 64, "\nProcessing $branch ($parent_branch)\n";    parent = argparse.Namespace(dir=patch.dir, name=parent, fn=patch.dir + parent + '.diff')
   
    if ($local_branch{$name}) {    if parent.name == args.base_branch:
        system 'git', 'branch', '-D', $branch and exit 1;        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 $!;    if patch.name in local_branch:
    print OUT $info;        cmd_chk(['git', 'branch', '-D', branch])
    close OUT; 
    system 'git', 'add', "PATCH.$name" and exit 1; 
   
    open IN, '<', $patch or die "Unable to open $patch: $!\n";    cmd_chk(['git', 'checkout', '-b', branch, parent_branch])
    $_ = join('', <IN>); 
    close IN; 
   
    open PIPE, '|-', 'patch -p1' or die $!;    info_fn = 'PATCH.' + patch.name
    print PIPE $_;    with open(info_fn, 'w', encoding='utf-8') as fh:
    close PIPE;        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) {    cmd_run('patch -p1'.split(), input=patch_txt)
        chmod oct($1), $2; 
        system 'git', 'add', $2; 
    } 
   
    while (1) {    for fn in glob.glob('*.orig') + glob.glob('*/*.orig'):
        system 'git status';        os.unlink(fn)
        print 'Press Enter to commit, Ctrl-C to abort, or type a wild-name to add a new file: '; 
        $_ = <STDIN>; 
        last if /^$/; 
        chomp; 
        system "git add $_"; 
    } 
   
    while (system 'git', 'commit', '-a', '-m', "Creating branch from $name.diff.") {    pos = 0
        exit 1 if system '/bin/zsh';    new_file_re = re.compile(r'\nnew file mode (?P<mode>\d+)\s+--- /dev/null\s+\+\+\+ b/(?P<fn>.+)')
    }    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    while True:
{        cmd_chk('git status'.split())
    die <<EOT;        ans = input('Press Enter to commit, Ctrl-C to abort, or type a wild-name to add a new file: ')
Usage branch-from-patch [OPTIONS] patches/DIFF...        if ans == '':
             break
         cmd_chk("git add " + ans, shell=True)
   
Options:    while True:
-b, --branch=BRANCH  Create branches relative to BRANCH if no "based-on"        s = cmd_run(['git', 'commit', '-a', '-m', f"Creating branch from {patch.name}.diff."])
                     header was found in the patch file.        if not s.returncode:
    --skip-check     Skip the check that ensures starting with a clean branch.            break
    --delete         Delete all the local patch/BASE/* branches, not just the ones        s = cmd_run(['/bin/zsh'])
                     that are being recreated.        if s.returncode:
-h, --help           Output this help message.            die('Aborting due to shell error code')
EOT
}
 if __name__ == '__main__':
     parser = argparse.ArgumentParser(description="Create a git patch branch from an rsync patch file.", add_help=False)
     parser.add_argument('--branch', '-b', dest='base_branch', metavar='BASE_BRANCH', default='master', help="The branch the patch is based on. Default: master.")
     parser.add_argument('--add-missing', '-a', action='store_true', help="Add a branch for every patches/*.diff that doesn't have a branch.")
     parser.add_argument('--skip-check', action='store_true', help="Skip the check that ensures starting with a clean branch.")
     parser.add_argument('--delete', dest='delete_local_branches', action='store_true', help="Delete all the local patch/BASE/* branches, not just the ones that are being recreated.")
     parser.add_argument('--patches-dir', '-p', metavar='DIR', default='patches', help="Override the location of the rsync-patches dir. Default: patches.")
     parser.add_argument('patch_files', metavar='patches/DIFF_FILE', nargs='*', help="Specify what patch diff files to process. Default: all of them.")
     parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.")
     args = parser.parse_args()
     main()
 
 # vim: sw=4 et ft=python

Removed from v.1.1.1.1  
changed lines
  Added in v.1.1.1.2


FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>