Diff for /embedaddon/rsync/packaging/patch-update between versions 1.1.1.3 and 1.1.1.4

version 1.1.1.3, 2016/11/01 09:54:32 version 1.1.1.4, 2021/03/17 00:32:36
Line 1 Line 1
#!/usr/bin/perl#!/usr/bin/env -S python3 -B
 
 # This script is used to turn one or more of the "patch/BASE/*" branches  # This script is used to turn one or more of the "patch/BASE/*" branches
 # into one or more diffs in the "patches" directory.  Pass the option  # into one or more diffs in the "patches" directory.  Pass the option
 # --gen if you want generated files in the diffs.  Pass the name of  # --gen if you want generated files in the diffs.  Pass the name of
 # one or more diffs if you want to just update a subset of all the  # one or more diffs if you want to just update a subset of all the
 # diffs.  # diffs.
   
use strict;import os, sys, re, argparse, time, shutil
use warnings; 
use Getopt::Long; 
   
my $patches_dir = 'patches';sys.path = ['packaging'] + sys.path
my $tmp_dir = "patches.$$"; 
my $make_gen_cmd = 'make -f prepare-source.mak conf && ./config.status && make gen'; 
   
&Getopt::Long::Configure('bundling');from pkglib import *
&usage if !&GetOptions( 
    'branch|b=s' => \( my $master_branch = 'master' ), 
    'skip-check' => \( my $skip_branch_check ), 
    'shell|s' => \( my $launch_shell ), 
    'gen:s' => \( my $incl_generated_files ), 
    'help|h' => \( my $help_opt ), 
); 
&usage if $help_opt; 
   
$ENV{GIT_MERGE_AUTOEDIT} = 'no';MAKE_GEN_CMDS = [
         './prepare-source'.split(),
         'cd build && if test -f config.status ; then ./config.status ; else ../configure ; fi',
         'make -C build gen'.split(),
         ]
 TMP_DIR = "patches.gen"
   
if (defined $incl_generated_files) {os.environ['GIT_MERGE_AUTOEDIT'] = 'no'
    $patches_dir = $incl_generated_files if $incl_generated_files ne ''; 
    $incl_generated_files = 1; 
} 
   
die "No '$patches_dir' directory was found.\n" unless -d $patches_dir;def main():
die "No '.git' directory present in the current dir.\n" unless -d '.git';    global master_commit, parent_patch, description, completed, last_touch
   
require 'packaging/git-status.pl';    if not os.path.isdir(args.patches_dir):
my $starting_branch = check_git_state($master_branch, !$skip_branch_check, 1);        die(f'No "{args.patches_dir}" directory was found.')
     if not os.path.isdir('.git'):
         die('No ".git" directory present in the current dir.')
   
my $master_commit;    starting_branch, args.base_branch = check_git_state(args.base_branch, not args.skip_check, args.patches_dir)
open PIPE, '-|', "git log -1 --no-color $master_branch" or die $!; 
while (<PIPE>) { 
    if (/^commit (\S+)/) { 
        $master_commit = $1; 
        last; 
    } 
} 
close PIPE; 
die "Unable to determine commit hash for master branch: $master_branch\n" unless defined $master_commit; 
   
if ($incl_generated_files) {    master_commit = latest_git_hash(args.base_branch)
    my @extra_files = get_extra_files(); 
    die "'$tmp_dir' must not exist in the current directory.\n" if -e $tmp_dir; 
    mkdir($tmp_dir, 0700) or die "Unable to mkdir($tmp_dir): $!\n"; 
    system "$make_gen_cmd && rsync -a @extra_files $tmp_dir/master/" and exit 1; 
} 
our $last_touch = time; 
   
my %patches;    if cmd_txt_chk(['packaging/prep-auto-dir']) == '':
         die('You must setup an auto-build-save dir to use this script.')
   
# Start by finding all patches so that we can load all possible parents.    if args.gen:
open(PIPE, '-|', 'git', 'branch', '-l') or die $!;        if os.path.lexists(TMP_DIR):
while (<PIPE>) {            die(f'"{TMP_DIR}" must not exist in the current directory.')
    if (m# patch/\Q$master_branch\E/(.*)#o) {        gen_files = get_gen_files()
        $patches{$1} = 1;        os.mkdir(TMP_DIR, 0o700)
    }        for cmd in MAKE_GEN_CMDS:
}            cmd_chk(cmd)
close PIPE;        cmd_chk(['rsync', '-a', *gen_files, f'{TMP_DIR}/master/'])
   
my @patches = sort keys %patches;    last_touch = int(time.time())
   
my(%parent, %description);    # Start by finding all patches so that we can load all possible parents.
foreach my $patch (@patches) {    patches = sorted(list(get_patch_branches(args.base_branch)))
    my $branch = "patch/$master_branch/$patch"; 
    my $desc = ''; 
    open(PIPE, '-|', 'git', 'diff', '-U1000', "$master_branch...$branch", '--', "PATCH.$patch") or die $!; 
    while (<PIPE>) { 
        last if /^@@ /; 
    } 
    while (<PIPE>) { 
        next unless s/^[ +]//; 
        if (m#patch -p1 <patches/(\S+)\.diff# && $1 ne $patch) { 
            my $parent = $parent{$patch} = $1; 
            if (!$patches{$parent}) { 
                die "Parent of $patch is not a local branch: $parent\n"; 
            } 
        } 
        $desc .= $_; 
    } 
    close PIPE; 
    $description{$patch} = $desc; 
} 
   
if (@ARGV) {    parent_patch = { }
    # Limit the list of patches to actually process based on @ARGV.    description = { }
    @patches = ( ); 
    foreach (@ARGV) { 
        s{^patch(es)?/} {}; 
        s{\.diff$} {}; 
        if (!$patches{$_}) { 
            die "Local branch not available for patch: $_\n"; 
        } 
        push(@patches, $_); 
    } 
} 
   
my %completed;    for patch in patches:
foreach my $patch (@patches) {        branch = f"patch/{args.base_branch}/{patch}"
    next if $completed{$patch}++;        desc = ''
    last unless update_patch($patch);        proc = cmd_pipe(['git', 'diff', '-U1000', f"{args.base_branch}...{branch}", '--', f"PATCH.{patch}"])
}        in_diff = False
         for line in proc.stdout:
             if in_diff:
                 if not re.match(r'^[ +]', line):
                     continue
                 line = line[1:]
                 m = re.search(r'patch -p1 <patches/(\S+)\.diff', line)
                 if m and m[1] != patch:
                     parpat = parent_patch[patch] = m[1]
                     if not parpat in patches:
                         die(f"Parent of {patch} is not a local branch: {parpat}")
                 desc += line
             elif re.match(r'^@@ ', line):
                 in_diff = True
         description[patch] = desc
         proc.communicate()
   
if ($incl_generated_files) {    if args.patch_files: # Limit the list of patches to actually process
    system "rm -rf $tmp_dir";        valid_patches = patches
}        patches = [ ]
         for fn in args.patch_files:
             name = re.sub(r'\.diff$', '', re.sub(r'.+/', '', fn))
             if name not in valid_patches:
                 die(f"Local branch not available for patch: {name}")
             patches.append(name)
   
sleep 1 while $last_touch >= time;    completed = set()
system "git checkout $starting_branch" and exit 1; 
   
exit;    for patch in patches:
         if patch in completed:
             continue
         if not update_patch(patch):
             break
   
       if args.gen:
           shutil.rmtree(TMP_DIR)
   
sub update_patch    while last_touch >= int(time.time()):
{        time.sleep(1)
    my($patch) = @_;    cmd_chk(['git', 'checkout', starting_branch])
     cmd_chk(['packaging/prep-auto-dir'], discard='output')
   
     my $parent = $parent{$patch};  
     my $based_on;  
     if (defined $parent) {  
         unless ($completed{$parent}++) {  
             update_patch($parent);  
         }  
         $based_on = $parent = "patch/$master_branch/$parent";  
     } else {  
         $parent = $master_branch;  
         $based_on = $master_commit;  
     }  
   
    print "======== $patch ========\n";def update_patch(patch):
     global last_touch
   
    sleep 1 while $incl_generated_files && $last_touch >= time;    completed.add(patch) # Mark it as completed early to short-circuit any (bogus) dependency loops.
    system "git checkout patch/$master_branch/$patch" and return 0; 
   
    my $ok = system("git merge $based_on") == 0;    parent = parent_patch.get(patch, None)
    if (!$ok || $launch_shell) {    if parent:
        my($parent_dir) = $parent =~ m{([^/]+)$};        if parent not in completed:
        print qq|"git merge $based_on" incomplete -- please fix.\n| if !$ok;            if not update_patch(parent):
        $ENV{PS1} = "[$parent_dir] $patch: ";                return 0
        while (1) {        based_on = parent = f"patch/{args.base_branch}/{parent}"
            if (system($ENV{SHELL}) != 0) {    else:
                print "Abort? [n/y] ";        parent = args.base_branch
                $_ = <STDIN>;        based_on = master_commit
                next unless /^y/i; 
                return 0; 
            } 
            my($cur_branch, $is_clean, $status) = check_git_status(0); 
            last if $is_clean; 
            print $status; 
        } 
    } 
   
    open(OUT, '>', "$patches_dir/$patch.diff") or die $!;    print(f"======== {patch} ========")
    print OUT $description{$patch}, "\nbased-on: $based_on\n"; 
   
    my @extra_files;    while args.gen and last_touch >= int(time.time()):
    if ($incl_generated_files) {        time.sleep(1)
        @extra_files = get_extra_files(); 
        system "$make_gen_cmd && rsync -a @extra_files $tmp_dir/$patch/" and exit 1; 
    } 
    $last_touch = time; 
   
    open(PIPE, '-|', 'git', 'diff', $based_on) or die $!;    branch = f"patch/{args.base_branch}/{patch}"
    DIFF: while (<PIPE>) {    s = cmd_run(['git', 'checkout', branch])
        while (m{^diff --git a/PATCH}) {    if s.returncode != 0:
            while (<PIPE>) {        return 0
                last if m{^diff --git a/}; 
            } 
            last DIFF if !defined $_; 
        } 
        next if /^index /; 
        print OUT $_; 
    } 
    close PIPE; 
   
    if ($incl_generated_files) {    s = cmd_run(['git', 'merge', based_on])
        my $parent_dir;    ok = s.returncode == 0
        if ($parent eq $master_branch) {    skip_shell = False
            $parent_dir = 'master';    if not ok or args.cmd or args.make or args.shell:
        } else {        cmd_chk(['packaging/prep-auto-dir'], discard='output')
            ($parent_dir) = $parent =~ m{([^/]+)$};    if not ok:
        }        print(f'"git merge {based_on}" incomplete -- please fix.')
        open(PIPE, '-|', 'diff', '-Nurp', "$tmp_dir/$parent_dir", "$tmp_dir/$patch") or die $!;        if not run_a_shell(parent, patch):
        while (<PIPE>) {            return 0
            s#^(diff -Nurp) $tmp_dir/[^/]+/(.*?) $tmp_dir/[^/]+/(.*)#$1 a/$2 b/$3#o;        if not args.make and not args.cmd:
            s#^\Q---\E $tmp_dir/[^/]+/([^\t]+)\t.*#--- a/$1#o;            skip_shell = True
            s#^\Q+++\E $tmp_dir/[^/]+/([^\t]+)\t.*#+++ b/$1#o;    if args.make:
            print OUT $_;        if cmd_run(['packaging/smart-make']).returncode != 0:
        }            if not run_a_shell(parent, patch):
        close PIPE;                return 0
        unlink @extra_files;            if not args.cmd:
    }                skip_shell = True
     if args.cmd:
         if cmd_run(args.cmd).returncode != 0:
             if not run_a_shell(parent, patch):
                 return 0
             skip_shell = True
     if args.shell and not skip_shell:
         if not run_a_shell(parent, patch):
             return 0
   
    close OUT;    with open(f"{args.patches_dir}/{patch}.diff", 'w', encoding='utf-8') as fh:
         fh.write(description[patch])
         fh.write(f"\nbased-on: {based_on}\n")
   
    1;        if args.gen:
}            gen_files = get_gen_files()
             for cmd in MAKE_GEN_CMDS:
                 cmd_chk(cmd)
             cmd_chk(['rsync', '-a', *gen_files, f"{TMP_DIR}/{patch}/"])
         else:
             gen_files = [ ]
         last_touch = int(time.time())
   
exit;        proc = cmd_pipe(['git', 'diff', based_on])
         skipping = False
         for line in proc.stdout:
             if skipping:
                 if not re.match(r'^diff --git a/', line):
                     continue
                 skipping = False
             elif re.match(r'^diff --git a/PATCH', line):
                 skipping = True
                 continue
             if not re.match(r'^index ', line):
                 fh.write(line)
         proc.communicate()
   
sub get_extra_files        if args.gen:
{            e_tmp_dir = re.escape(TMP_DIR)
    my @extras;            diff_re  = re.compile(r'^(diff -Nurp) %s/[^/]+/(.*?) %s/[^/]+/(.*)' % (e_tmp_dir, e_tmp_dir))
             minus_re = re.compile(r'^\-\-\- %s/[^/]+/([^\t]+)\t.*' % e_tmp_dir)
             plus_re  = re.compile(r'^\+\+\+ %s/[^/]+/([^\t]+)\t.*' % e_tmp_dir)
   
    open(IN, '<', 'Makefile.in') or die "Couldn't open Makefile.in: $!\n";            if parent == args.base_branch:
    while (<IN>) {                parent_dir = 'master'
        if (s/^GENFILES=//) {            else:
            while (s/\\$//) {                m = re.search(r'([^/]+)$', parent)
                $_ .= <IN>;                parent_dir = m[1]
            } 
            @extras = split(' ', $_); 
            last; 
        } 
    } 
    close IN; 
   
    return @extras;            proc = cmd_pipe(['diff', '-Nurp', f"{TMP_DIR}/{parent_dir}", f"{TMP_DIR}/{patch}"])
}            for line in proc.stdout:
                 line = diff_re.sub(r'\1 a/\2 b/\3', line)
                 line = minus_re.sub(r'--- a/\1', line)
                 line =  plus_re.sub(r'+++ b/\1', line)
                 fh.write(line)
             proc.communicate()
   
sub usage    return 1
{ 
    die <<EOT; 
Usage: patch-update [OPTIONS] [patches/DIFF...] 
   
Options:
-b, --branch=BRANCH  The master branch to merge into the patch/BASE/* branches.def run_a_shell(parent, patch):
    --gen[=DIR]      Include generated files.  Optional destination DIR    m = re.search(r'([^/]+)$', parent)
                     arg overrides the default of using the "patches" dir.    parent_dir = m[1]
    --skip-check     Skip the check that ensures starting with a clean branch.    os.environ['PS1'] = f"[{parent_dir}] {patch}: "
-s, --shell          Launch a shell for every patch/BASE/* branch updated, not
                     just when a conflict occurs.    while True:
-h, --help           Output this help message.        s = cmd_run([os.environ.get('SHELL', '/bin/sh')])
EOT        if s.returncode != 0:
}            ans = input("Abort? [n/y] ")
             if re.match(r'^y', ans, flags=re.I):
                 return False
             continue
         cur_branch, is_clean, status_txt = check_git_status(0)
         if is_clean:
             break
         print(status_txt, end='')
 
     cmd_run('rm -f build/*.o build/*/*.o')
 
     return True
 
 
 if __name__ == '__main__':
     parser = argparse.ArgumentParser(description="Turn a git branch back into a diff files in the patches dir.", 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('--skip-check', action='store_true', help="Skip the check that ensures starting with a clean branch.")
     parser.add_argument('--make', '-m', action='store_true', help="Run the smart-make script in every patch branch.")
     parser.add_argument('--cmd', '-c', help="Run a command in every patch branch.")
     parser.add_argument('--shell', '-s', action='store_true', help="Launch a shell for every patch/BASE/* branch updated, not just when a conflict occurs.")
     parser.add_argument('--gen', metavar='DIR', nargs='?', const='', help='Include generated files. Optional DIR value overrides the default of using the "patches" dir.')
     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()
     if args.gen == '':
         args.gen = args.patches_dir
     elif args.gen is not None:
         args.patches_dir = args.gen
     main()
 
 # vim: sw=4 et ft=python

Removed from v.1.1.1.3  
changed lines
  Added in v.1.1.1.4


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