From: Wayne Davison Date: Sun, 12 Apr 2020 07:13:35 +0000 (-0700) Subject: Change some packaging tools into python3 and make a few improvements. X-Git-Tag: v3.2.0pre1~172 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=2d0c7adba9915b8a4ae379f351775a8b314671c0;p=thirdparty%2Frsync.git Change some packaging tools into python3 and make a few improvements. --- diff --git a/packaging/branch-from-patch b/packaging/branch-from-patch index a66cb16f..493f557d 100755 --- a/packaging/branch-from-patch +++ b/packaging/branch-from-patch @@ -1,182 +1,174 @@ -#!/usr/bin/perl - -use strict; -use warnings; -use Getopt::Long; - -&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; - -push @INC, '.'; - -require 'packaging/git-status.pl'; -check_git_state($master_branch, !$skip_branch_check, 1); - -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; - -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 = ( ); -} - -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, $_; -} - -exit unless @patch_list; - -my(%scanned, %created, %info); - -foreach my $patch (@patch_list) { - my($where, $name) = $patch =~ m{^(.*?)([^/]+)\.diff$}; - next if $scanned{$name}++; - - open IN, '<', $patch or die "Unable to open $patch: $!\n"; - - 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; - - $info =~ s/\s+\Z/\n/; - - my $parent = $master_branch; - my @patches = $info =~ m#patch -p1 ', "PATCH.$name" or die $!; - print OUT $info; - close OUT; - system 'git', 'add', "PATCH.$name" and exit 1; - - open IN, '<', $patch or die "Unable to open $patch: $!\n"; - $_ = join('', ); - close IN; - - open PIPE, '|-', 'patch -p1' or die $!; - print PIPE $_; - close PIPE; - - system 'rm -f *.orig */*.orig'; - - while (m#\nnew file mode (\d+)\s+--- /dev/null\s+\Q+++\E b/(.*)#g) { - chmod oct($1), $2; - system 'git', 'add', $2; - } - - 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 $_"; - } - - while (system 'git', 'commit', '-a', '-m', "Creating branch from $name.diff.") { - exit 1 if system '/bin/zsh'; - } -} - -sub usage -{ - die <.*?)(?P[^/]+)\.diff$', fn) + patch = argparse.Namespace(**m.groupdict()) + if patch.name in scanned: + continue + patch.fn = fn + + 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)) + info_txt = ''.join(lines).strip() + "\n" + lines = None + + 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}") + + info[patch.name] = [ parent, info_txt, commit_hash ] + + patch_list.append(patch) + + created = set() + for patch in patch_list: + create_branch(patch) + + cmd_chk(['git', 'checkout', args.base_branch]) + + +def create_branch(patch): + if patch.name in created: + return + created.add(patch.name) + + parent, info_txt, commit_hash = info[patch.name] + parent = argparse.Namespace(dir=patch.dir, name=parent, fn=patch.dir + parent + '.diff') + + 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]) + + branch = '/'.join(['patch', args.base_branch, patch.name]) + print("\n" + '=' * 64) + print(f"Processing {branch} ({parent_branch})") + + if patch.name in local_branch: + cmd_chk(['git', 'branch', '-D', branch]) + + cmd_chk(['git', 'checkout', '-b', branch, parent_branch]) + + 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]) + + with open(patch.fn, 'r', encoding='utf-8') as fh: + patch_txt = fh.read() + + cmd_run('patch -p1'.split(), input=patch_txt) + + for fn in glob.glob('*.orig') + glob.glob('*/*.orig'): + os.unlink(fn) + + 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() + + while True: + cmd_chk('git status'.split()) + ans = input('Press Enter to commit, Ctrl-C to abort, or type a wild-name to add a new file: ') + if ans == '': + break + cmd_chk("git add " + ans, shell=True) + + while True: + s = cmd_run(['git', 'commit', '-a', '-m', f"Creating branch from {patch.name}.diff."]) + if not s.returncode: + break + s = cmd_run(['/bin/zsh']) + if s.returncode: + die('Aborting due to shell error code') + + +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 diff --git a/packaging/git-status.pl b/packaging/git-status.pl deleted file mode 100644 index f68023ac..00000000 --- a/packaging/git-status.pl +++ /dev/null @@ -1,52 +0,0 @@ -# Do some git-status checking for the current dir and (optionally) -# the patches dir. - -sub check_git_state -{ - my($master_branch, $fatal_unless_clean, $check_patches_dir) = @_; - - my($cur_branch) = check_git_status($fatal_unless_clean); - (my $branch = $cur_branch) =~ s{^patch/([^/]+)/[^/]+$}{$1}; # change patch/BRANCH/PATCH_NAME into BRANCH - if ($branch ne $master_branch) { - print "The checkout is not on the $master_branch branch.\n"; - exit 1 if $master_branch ne 'master'; - print "Do you want me to continue with --branch=$branch? [n] "; - $_ = ; - exit 1 unless /^y/i; - $_[0] = $master_branch = $branch; # Updates caller's $master_branch too. - } - - if ($check_patches_dir && -d 'patches/.git') { - ($branch) = check_git_status($fatal_unless_clean, 'patches'); - if ($branch ne $master_branch) { - print "The *patches* checkout is on branch $branch, not branch $master_branch.\n"; - print "Do you want to change it to branch $master_branch? [n] "; - $_ = ; - exit 1 unless /^y/i; - system "cd patches && git checkout '$master_branch'"; - } - } - - return $cur_branch; -} - -sub check_git_status -{ - my($fatal_unless_clean, $subdir) = @_; - $subdir = '.' unless defined $subdir; - my $status = `cd '$subdir' && git status`; - my $is_clean = $status =~ /\nnothing to commit.+working (directory|tree) clean/; - - my($cur_branch) = $status =~ /^(?:# )?On branch (.+)\n/; - if ($fatal_unless_clean && !$is_clean) { - if ($subdir eq '.') { - $subdir = ''; - } else { - $subdir = " *$subdir*"; - } - die "The$subdir checkout is not clean:\n", $status; - } - ($cur_branch, $is_clean, $status); -} - -1; diff --git a/packaging/patch-update b/packaging/patch-update index e279c6bc..e13679c0 100755 --- a/packaging/patch-update +++ b/packaging/patch-update @@ -1,244 +1,210 @@ -#!/usr/bin/perl +#!/usr/bin/python3 -B + # 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 # --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 # diffs. -use strict; -use warnings; -use Getopt::Long; - -my $patches_dir = 'patches'; -my $tmp_dir = "patches.$$"; -my $make_gen_cmd = 'make -f prepare-source.mak conf && ./config.status && make gen'; - -&Getopt::Long::Configure('bundling'); -&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'; - -if (defined $incl_generated_files) { - $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; -die "No '.git' directory present in the current dir.\n" unless -d '.git'; - -push @INC, '.'; - -require 'packaging/git-status.pl'; -my $starting_branch = check_git_state($master_branch, !$skip_branch_check, 1); - -my $master_commit; -open PIPE, '-|', "git log -1 --no-color $master_branch" or die $!; -while () { - 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) { - 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; - -# Start by finding all patches so that we can load all possible parents. -open(PIPE, '-|', 'git', 'branch', '-l') or die $!; -while () { - if (m# patch/\Q$master_branch\E/(.*)#o) { - $patches{$1} = 1; - } -} -close PIPE; - -my @patches = sort keys %patches; - -my(%parent, %description); -foreach my $patch (@patches) { - my $branch = "patch/$master_branch/$patch"; - my $desc = ''; - open(PIPE, '-|', 'git', 'diff', '-U1000', "$master_branch...$branch", '--', "PATCH.$patch") or die $!; - while () { - last if /^@@ /; - } - while () { - next unless s/^[ +]//; - if (m#patch -p1 = time; -system "git checkout $starting_branch" and exit 1; - -exit; - - -sub update_patch -{ - my($patch) = @_; - - 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"; - - sleep 1 while $incl_generated_files && $last_touch >= time; - system "git checkout patch/$master_branch/$patch" and return 0; - - my $ok = system("git merge $based_on") == 0; - if (!$ok || $launch_shell) { - my($parent_dir) = $parent =~ m{([^/]+)$}; - print qq|"git merge $based_on" incomplete -- please fix.\n| if !$ok; - $ENV{PS1} = "[$parent_dir] $patch: "; - while (1) { - if (system($ENV{SHELL}) != 0) { - print "Abort? [n/y] "; - $_ = ; - 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 OUT $description{$patch}, "\nbased-on: $based_on\n"; - - my @extra_files; - if ($incl_generated_files) { - @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 $!; - DIFF: while () { - while (m{^diff --git a/PATCH}) { - while () { - last if m{^diff --git a/}; - } - last DIFF if !defined $_; - } - next if /^index /; - print OUT $_; - } - close PIPE; - - if ($incl_generated_files) { - my $parent_dir; - if ($parent eq $master_branch) { - $parent_dir = 'master'; - } else { - ($parent_dir) = $parent =~ m{([^/]+)$}; - } - open(PIPE, '-|', 'diff', '-Nurp', "$tmp_dir/$parent_dir", "$tmp_dir/$patch") or die $!; - while () { - s#^(diff -Nurp) $tmp_dir/[^/]+/(.*?) $tmp_dir/[^/]+/(.*)#$1 a/$2 b/$3#o; - s#^\Q---\E $tmp_dir/[^/]+/([^\t]+)\t.*#--- a/$1#o; - s#^\Q+++\E $tmp_dir/[^/]+/([^\t]+)\t.*#+++ b/$1#o; - print OUT $_; - } - close PIPE; - unlink @extra_files; - } - - close OUT; - - 1; -} - -exit; - -sub get_extra_files -{ - my @extras; - - open(IN, '<', 'Makefile.in') or die "Couldn't open Makefile.in: $!\n"; - while () { - if (s/^GENFILES=//) { - while (s/\\$//) { - $_ .= ; - } - @extras = split(' ', $_); - last; - } - } - close IN; - - return @extras; -} - -sub usage -{ - die <= time.time(): + time.sleep(1) + cmd_chk(['git', 'checkout', starting_branch]) + + +def update_patch(patch): + global last_touch + + completed.add(patch) # Mark it as completed early to short-circuit any (bogus) dependency loops. + + parent = parent_patch.get(patch, None) + if parent: + if parent not in completed: + if not update_patch(parent): + return 0 + based_on = parent = f"patch/{args.base_branch}/{parent}" + else: + parent = args.base_branch + based_on = master_commit + + print(f"======== {patch} ========") + + while args.gen and last_touch >= time.time(): + time.sleep(1) + s = cmd_run(f"git checkout patch/{args.base_branch}/{patch}".split()) + if s.returncode != 0: + return 0 + + s = cmd_run(['git', 'merge', based_on]) + ok = s.returncode == 0 + if not ok or args.shell: + m = re.search(r'([^/]+)$', parent) + parent_dir = m[1] + if not ok: + print(f'"git merge {based_on}" incomplete -- please fix.') + os.environ['PS1'] = f"[{parent_dir}] {patch}: " + while True: + s = cmd_run([os.environ.get('SHELL', '/bin/sh')]) + if s.returncode != 0: + ans = input("Abort? [n/y] ") + if re.match(r'^y', ans, flags=re.I): + return 0 + continue + cur_branch, is_clean, status_txt = check_git_status(0) + if is_clean: + break + print(status_txt, end='') + + 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") + + if args.gen: + extra_files = get_extra_files() + for cmd in MAKE_GEN_CMDS: + cmd_chk(cmd) + cmd_chk(['rsync', '-a', *extra_files, f"{TMP_DIR}/{patch}/"]) + else: + extra_files = [ ] + last_touch = time.time() + + 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() + + if args.gen: + e_tmp_dir = re.escape(TMP_DIR) + 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) + + if parent == args.base_branch: + parent_dir = 'master' + else: + m = re.search(r'([^/]+)$', parent) + parent_dir = m[1] + + 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() + for fn in extra_files: + os.unlink(fn) + + return 1 + + +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('--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 diff --git a/packaging/pkglib.py b/packaging/pkglib.py new file mode 100644 index 00000000..fd6359e4 --- /dev/null +++ b/packaging/pkglib.py @@ -0,0 +1,160 @@ +import os, sys, re, subprocess + +# This python3 library provides a few helpful routines that are +# used by the latest packaging scripts. + +# Output the msg args to stderr. Accepts all the args that print() accepts. +def warn(*msg): + print(*msg, file=sys.stderr) + + +# Output the msg args to stderr and die with a non-zero return-code. +# Accepts all the args that print() accepts. +def die(*msg): + warn(*msg) + sys.exit(1) + + +def _tweak_opts(cmd, opts): + if type(cmd) == str: + opts['shell'] = True + if not 'encoding' in opts: + if opts.get('raw', False): + del opts['raw'] + else: + opts['encoding'] = 'utf-8' + + +# This does a normal subprocess.run() with some auto-args added to make life easier. +def cmd_run(cmd, **opts): + _tweak_opts(cmd, opts) + return subprocess.run(cmd, **opts) + + +# Works like cmd_run() with a default check=True specified for you. +def cmd_chk(cmd, **opts): + return cmd_run(cmd, check=True, **opts) + + +# Captures stdout & stderr together in a string and returns the (output, return-code) tuple. +def cmd_txt(cmd, **opts): + opts['stdout'] = subprocess.PIPE + opts['stderr'] = subprocess.STDOUT + _tweak_opts(cmd, opts) + proc = subprocess.Popen(cmd, **opts) + out = proc.communicate()[0] + return (out, proc.returncode) + + +# Captures stdout & stderr together in a string and returns the output if the command has a 0 +# return-code. Otherwise it throws an exception that indicates the return code and all the +# captured output. +def cmd_txt_chk(cmd, **opts): + out, rc = cmd_txt(cmd, **opts) + if rc != 0: + cmd_err = f'Command "{cmd}" returned non-zero exit status "{rc}" and output:\n{out}' + raise Exception(cmd_err) + return out + + +# Starts a piped-output command of just stdout (by default) and leaves it up to you to do +# any incremental reading of the output and to call communicate() on the returned object. +def cmd_pipe(cmd, **opts): + opts['stdout'] = subprocess.PIPE + _tweak_opts(cmd, opts) + return subprocess.Popen(cmd, **opts) + + +# Runs a "git status" command and dies if the checkout is not clean (the +# arg fatal_unless_clean can be used to make that non-fatal. Returns a +# tuple of the current branch, the is_clean flag, and the status text. +def check_git_status(fatal_unless_clean=True, subdir='.'): + status_txt = cmd_txt_chk(f"cd '{subdir}' && git status") + is_clean = re.search(r'\nnothing to commit.+working (directory|tree) clean', status_txt) != None + + if not is_clean and fatal_unless_clean: + if subdir == '.': + subdir = '' + else: + subdir = f" *{subdir}*" + die(f"The{subdir} checkout is not clean:\n" + status_txt) + + m = re.match(r'^(?:# )?On branch (.+)\n', status_txt) + cur_branch = m[1] if m else None + + return (cur_branch, is_clean, status_txt) + + +# Calls check_git_status() on the current git checkout and (optionally) a subdir path's +# checkout. Use fatal_unless_clean to indicate if an unclean checkout is fatal or not. +# The master_branch arg indicates what branch we want both checkouts to be using, and +# if the branch is wrong the user is given the option of either switching to the right +# branch or aborting. +def check_git_state(master_branch, fatal_unless_clean=True, check_extra_dir=None): + cur_branch = check_git_status(fatal_unless_clean)[0] + branch = re.sub(r'^patch/([^/]+)/[^/]+$', r'\1', cur_branch) # change patch/BRANCH/PATCH_NAME into BRANCH + if branch != master_branch: + print(f"The checkout is not on the {master_branch} branch.") + if master_branch != 'master': + sys.exit(1) + ans = input(f"Do you want me to continue with --branch={branch}? [n] ") + if not ans or not re.match(r'^y', ans, flags=re.I): + sys.exit(1) + master_branch = branch + + if check_extra_dir and os.path.isdir(os.path.join(check_extra_dir, '.git')): + branch = check_git_status(fatal_unless_clean, check_extra_dir)[0] + if branch != master_branch: + print(f"The *{check_extra_dir}* checkout is on branch {branch}, not branch {master_branch}.") + ans = input(f"Do you want to change it to branch {master_branch}? [n] ") + if not ans or not re.match(r'^y', ans, flags=re.I): + sys.exit(1) + subdir.check_call(f"cd {check_extra_dir} && git checkout '{master_branch}'", shell=True) + + return (cur_branch, master_branch) + + +# Return the git hash of the most recent commit. +def latest_git_hash(branch): + out = cmd_txt_chk(['git', 'log', '-1', '--no-color', branch]) + m = re.search(r'^commit (\S+)', out, flags=re.M) + if not m: + die(f"Unable to determine commit hash for master branch: {branch}") + return m[1] + + +# Return a set of all branch names that have the format "patch/BASE_BRANCH/NAME" +# for the given base_branch string. Just the NAME portion is put into the set. +def get_patch_branches(base_branch): + branches = set() + proc = cmd_pipe('git branch -l'.split()) + for line in proc.stdout: + m = re.search(r' patch/([^/]+)/(.+)', line) + if m and m[1] == base_branch: + branches.add(m[2]) + proc.communicate() + return branches + + +# Snag the GENFILES values out of the Makefile.in file and return them as a list. +def get_extra_files(): + cont_re = re.compile(r'\\\n') + + extras = [ ] + + with open('Makefile.in', 'r', encoding='utf-8') as fh: + for line in fh: + if not extras: + chk = re.sub(r'^GENFILES=', '', line) + if line == chk: + continue + line = chk + m = re.search(r'\\$', line) + line = re.sub(r'^\s+|\s*\\\n?$|\s+$', '', line) + extras += line.split() + if not m: + break + + return extras + +# vim: sw=4 et diff --git a/packaging/release-rsync b/packaging/release-rsync index fdb69efc..20d8ea07 100755 --- a/packaging/release-rsync +++ b/packaging/release-rsync @@ -1,183 +1,190 @@ -#!/usr/bin/perl +#!/usr/bin/python3 -B + # This script expects the directory ~/samba-rsync-ftp to exist and to be a # copy of the /home/ftp/pub/rsync dir on samba.org. When the script is done, # the git repository in the current directory will be updated, and the local # ~/samba-rsync-ftp dir will be ready to be rsynced to samba.org. -use strict; -use warnings; -use Cwd; -use Getopt::Long; -use Term::ReadKey; -use Date::Format; - -my $dest = $ENV{HOME} . '/samba-rsync-ftp'; -my $passfile = $ENV{HOME} . '/.rsyncpass'; -my $path = $ENV{PATH}; -my $make_gen_cmd = 'make -f prepare-source.mak conf && ./config.status && make gen'; - -&Getopt::Long::Configure('bundling'); -&usage if !&GetOptions( - 'branch|b=s' => \( my $master_branch = 'master' ), - 'help|h' => \( my $help_opt ), -); -&usage if $help_opt; - -my $now = time; -my $cl_today = time2str('* %a %b %d %Y', $now); -my $year = time2str('%Y', $now); -my $ztoday = time2str('%d %b %Y', $now); -(my $today = $ztoday) =~ s/^0//; - -my $curdir = Cwd::cwd; - -END { - unlink($passfile); -} - -my @extra_files; -open(IN, '<', 'Makefile.in') or die "Couldn't open Makefile.in: $!\n"; -while () { - if (s/^GENFILES=//) { - while (s/\\$//) { - $_ .= ; - } - @extra_files = split(' ', $_); - last; - } -} -close IN; - -my $break = <\d+\.\d+\.\d+)\s+(?P\d{2} \w{3} \d{4}\s+)?(?P\d+)$') + +def main(): + now = datetime.now() + cl_today = now.strftime('* %a %b %d %Y') + year = now.strftime('%Y') + ztoday = now.strftime('%d %b %Y') + today = ztoday.lstrip('0') + + curdir = os.getcwd() + + atexit.register(remove_passfile) + signal.signal(signal.SIGINT, signal_handler) + + extra_files = get_extra_files() + + dash_line = '=' * 74 + + print(f"""\ +{dash_line} == This will release a new version of rsync onto an unsuspecting world. == -EOT - -die "$dest does not exist\n" unless -d $dest; -die "There is no .git dir in the current directory.\n" unless -d '.git'; -die "'a' must not exist in the current directory.\n" if -e 'a'; -die "'b' must not exist in the current directory.\n" if -e 'b'; - -require 'packaging/git-status.pl'; -check_git_state($master_branch, 1, 1); - -my $confversion; -open(IN, '<', 'configure.ac') or die $!; -while () { - if (/^AC_INIT\(\[rsync\],\s*\[(\d.+?)\]/) { - $confversion = $1; - last; - } -} -close IN; -die "Unable to find AC_INIT with version in configure.ac\n" unless defined $confversion; - -open(IN, '<', 'OLDNEWS') or die $!; -$_ = ; -my($lastversion) = /(\d+\.\d+\.\d+)/; -my($last_protocol_version, %pdate); -while () { - if (my($ver,$pdate,$pver) = /^\s+\S\S\s\S\S\S\s\d\d\d\d\s+(\d+\.\d+\.\d+)\s+(\d\d \w\w\w \d\d\d\d\s+)?(\d+)$/) { - $pdate{$ver} = $pdate if defined $pdate; - $last_protocol_version = $pver if $ver eq $lastversion; - } -} -close IN; -die "Unable to determine protocol_version for $lastversion.\n" unless defined $last_protocol_version; - -my $protocol_version; -open(IN, '<', 'rsync.h') or die $!; -while () { - if (/^#define\s+PROTOCOL_VERSION\s+(\d+)/) { - $protocol_version = $1; - last; - } -} -close IN; -die "Unable to determine the current PROTOCOL_VERSION.\n" unless defined $protocol_version; - -my $version = $confversion; -$version =~ s/dev/pre1/ || $version =~ s/pre(\d+)/ 'pre' . ($1 + 1) /e; - -print "Please enter the version number of this release: [$version] "; -chomp($_ = ); -if ($_ eq '.') { - $version =~ s/pre\d+//; -} elsif ($_ ne '') { - $version = $_; -} -die "Invalid version: `$version'\n" unless $version =~ /^[\d.]+(pre\d+)?$/; - -if (`git tag -l v$version` ne '') { - print "Tag v$version already exists.\n\nDelete tag or quit? [q/del] "; - $_ = ; - exit 1 unless /^del/i; - system "git tag -d v$version"; -} - -if ($version =~ s/[-.]*pre[-.]*/pre/ && $confversion !~ /dev$/) { - $lastversion = $confversion; -} - -print "Enter the previous version to produce a patch against: [$lastversion] "; -chomp($_ = ); -$lastversion = $_ if $_ ne ''; -$lastversion =~ s/[-.]*pre[-.]*/pre/; - -my $pre = $version =~ /(pre\d+)/ ? $1 : ''; - -my $release = $pre ? '0.1' : '1'; -print "Please enter the RPM release number of this release: [$release] "; -chomp($_ = ); -$release = $_ if $_ ne ''; -$release .= ".$pre" if $pre; - -(my $finalversion = $version) =~ s/pre\d+//; -my($proto_changed,$proto_change_date); -if ($protocol_version eq $last_protocol_version) { - $proto_changed = 'unchanged'; - $proto_change_date = "\t\t"; -} else { - $proto_changed = 'changed'; - if (!defined($proto_change_date = $pdate{$finalversion})) { - while (1) { - print "On what date did the protocol change to $protocol_version get checked in? (dd Mmm yyyy) "; - chomp($_ = ); - last if /^\d\d \w\w\w \d\d\d\d$/; - } - $proto_change_date = "$_\t"; - } -} - -my($srcdir,$srcdiffdir,$lastsrcdir,$skipping); -if ($lastversion =~ /pre/) { - if (!$pre) { - die "You should not diff a release version against a pre-release version.\n"; - } - $srcdir = $srcdiffdir = $lastsrcdir = 'src-previews'; - $skipping = ' ** SKIPPING **'; -} elsif ($pre) { - $srcdir = $srcdiffdir = 'src-previews'; - $lastsrcdir = 'src'; - $skipping = ' ** SKIPPING **'; -} else { - $srcdir = $lastsrcdir = 'src'; - $srcdiffdir = 'src-diffs'; - $skipping = ''; -} - -print "\n", $break, < "; -$_ = ; - -my %specvars = ( 'Version:' => $finalversion, 'Release:' => $release, - '%define fullversion' => "\%{version}$pre", 'Released' => "$version.", - '%define srcdir' => $srcdir ); -my @tweak_files = ( glob('packaging/*.spec'), glob('packaging/*/*.spec'), glob('*.yo'), - qw( configure.ac rsync.h NEWS OLDNEWS options.c ) ); - -foreach my $fn (@tweak_files) { - open(IN, '<', $fn) or die $!; - undef $/; $_ = ; $/ = "\n"; - close IN; - if ($fn =~ /configure/) { - s/^(AC_INIT\(\[rsync\],\s*\[)\d.+?(\])/$1$version$2/m - or die "Unable to update AC_INIT with version in $fn\n"; - } elsif ($fn =~ /\.spec/) { - while (my($str, $val) = each %specvars) { - s/^\Q$str\E .*/$str $val/m - or die "Unable to update $str in $fn\n"; - } - s/^\* \w\w\w \w\w\w \d\d \d\d\d\d (.*)/$cl_today $1/m - or die "Unable to update ChangeLog header in $fn\n"; - } elsif ($fn =~ /\.yo/) { - s/^(manpage\([^)]+\)\(\d+\)\()[^)]+(\).*)/$1$today$2/m - or die "Unable to update date in manpage() header in $fn\n"; - s/^(This man ?page is current for version) \S+ (of rsync)/$1 $version $2/m - or die "Unable to update current version info in $fn\n"; - } elsif ($fn eq 'rsync.h') { - s{(#define\s+SUBPROTOCOL_VERSION)\s+(\d+)} - { $1 . ' ' . get_subprotocol_version($2) }e - or die "Unable to find SUBPROTOCOL_VERSION define in $fn\n"; - } elsif ($fn eq 'NEWS') { - s{^(NEWS for rsync \Q$finalversion\E )(\(UNRELEASED\))\s*(\nProtocol: )(\d+) (\([^)]+\))\n} - { $1 . ($pre ? $2 : "($today)") . "$3$protocol_version ($proto_changed)\n" }ei - or die "The first 2 lines of $fn are not in the right format. They must be:\n" - . "NEWS for rsync $finalversion (UNRELEASED)\n" - . "Protocol: $protocol_version ($proto_changed)\n"; - } elsif ($fn eq 'OLDNEWS') { - s{^(\t\S\S\s\S\S\S\s\d\d\d\d)(\t\Q$finalversion\E\t).*} - { ($pre ? $1 : "\t$ztoday") . $2 . $proto_change_date . $protocol_version }em - or die "Unable to find \"?? ??? $year\t$finalversion\" line in $fn\n"; - } elsif ($fn eq 'options.c') { - if (s/(Copyright \(C\) 2002-)(\d+)( Wayne Davison)/$1$year$3/ - && $2 ne $year) { - die "Copyright comments need to be updated to $year in all files!\n"; - } - # Adjust the year in the --version output. - s/(rprintf\(f, "Copyright \(C\) 1996-)(\d+)/$1$year/ - or die "Unable to find Copyright string in --version output of $fn\n"; - next if $2 eq $year; - } else { - die "Unrecognized file in \@tweak_files: $fn\n"; - } - open(OUT, '>', $fn) or die $!; - print OUT $_; - close OUT; -} - -print $break; -system "git diff --color | less -p '^diff .*'"; - -my $srctar_name = "rsync-$version.tar.gz"; -my $pattar_name = "rsync-patches-$version.tar.gz"; -my $diff_name = "rsync-$lastversion-$version.diffs.gz"; -my $srctar_file = "$dest/$srcdir/$srctar_name"; -my $pattar_file = "$dest/$srcdir/$pattar_name"; -my $diff_file = "$dest/$srcdiffdir/$diff_name"; -my $news_file = "$dest/$srcdir/rsync-$version-NEWS"; -my $lasttar_file = "$dest/$lastsrcdir/rsync-$lastversion.tar.gz"; - -print $break, < ") + + specvars = { + 'Version:': finalversion, + 'Release:': release, + '%define fullversion': f'%{{version}}{pre}', + 'Released': version + '.', + '%define srcdir': srcdir, + } + + tweak_files = 'configure.ac rsync.h NEWS OLDNEWS'.split() + tweak_files += glob.glob('packaging/*.spec') + tweak_files += glob.glob('packaging/*/*.spec') + tweak_files += glob.glob('*.yo') + + for fn in tweak_files: + with open(fn, 'r', encoding='utf-8') as fh: + old_txt = txt = fh.read() + if 'configure' in fn: + x_re = re.compile(r'^(AC_INIT\(\[rsync\],\s*\[)\d.+?(\])', re.M) + txt = replace_or_die(x_re, r'\g<1>%s\2' % version, txt, f"Unable to update AC_INIT with version in {fn}") + elif '.spec' in fn: + for var, val in specvars.items(): + x_re = re.compile(r'^%s .*' % re.escape(var), re.M) + txt = replace_or_die(x_re, var + ' ' + val, txt, f"Unable to update {var} in {fn}") + x_re = re.compile(r'^\* \w\w\w \w\w\w \d\d \d\d\d\d (.*)', re.M) + txt = replace_or_die(x_re, r'%s \1' % cl_today, txt, f"Unable to update ChangeLog header in {fn}") + elif '.yo' in fn: + x_re = re.compile(r'^(manpage\([^)]+\)\(\d+\)\()[^)]+(\).*)', re.M) + txt = replace_or_die(x_re, r'\g<1>%s\2' % today, txt, f"Unable to update date in manpage() header in {fn}") + x_re = re.compile(r'^(This man ?page is current for version) \S+ (of rsync)', re.M) + txt = replace_or_die(x_re, r'\1 %s \2' % version, txt, f"Unable to update current version info in {fn}") + elif fn == 'rsync.h': + x_re = re.compile('(#define\s+SUBPROTOCOL_VERSION)\s+(\d+)') + repl = lambda m: m[1] + ' ' + '0' if not pre or proto_changed != 'changed' else 1 if m[2] == '0' else m[2] + txt = replace_or_die(x_re, repl, txt, f"Unable to find SUBPROTOCOL_VERSION define in {fn}") + elif fn == 'NEWS': + x_re = re.compile( + r'^(NEWS for rsync %s )(\(UNRELEASED\))\s*(\nProtocol: )(\d+) (\([^)]+\))\n' % re.escape(finalversion), + re.I) + repl = lambda m: m[1] + (m[2] if pre else f"({today})") + m[3] + f"{protocol_version} ({proto_changed})\n" + msg = (f"The first 2 lines of {fn} are not in the right format. They must be:\n" + + f"NEWS for rsync {finalversion} (UNRELEASED)\n" + + f"Protocol: {protocol_version} ({proto_changed})") + txt = replace_or_die(x_re, repl, txt, msg) + elif fn == 'OLDNEWS': + x_re = re.compile(r'^(\t\S\S\s\S\S\S\s\d\d\d\d)(\t%s\t).*' % re.escape(finalversion), re.M) + repl = lambda m: (m[1] if pre else "\t" + ztoday) + m[2] + proto_change_date + protocol_version + txt = replace_or_die(x_re, repl, txt, f'Unable to find "?? ??? {year}\t{finalversion}" line in {fn}') + else: + die(f"Unrecognized file in tweak_files: {fn}") + + if txt != old_txt: + print(f"Updating {fn}") + with open(fn, 'w', encoding='utf-8') as fh: + fh.write(txt) + + cmd_chk(['packaging/year-tweak']) + + print(dash_line) + cmd_run("git diff --color | less -p '^diff .*'") + + srctar_name = f"{rsync_ver}.tar.gz" + pattar_name = f"rsync-patches-{version}.tar.gz" + diff_name = f"{rsync_lastver}-{version}.diffs.gz" + srctar_file = f"{dest}/{srcdir}/{srctar_name}" + pattar_file = f"{dest}/{srcdir}/{pattar_name}" + diff_file = f"{dest}/{srcdiffdir}/{diff_name}" + news_file = f"{dest}/{srcdir}/{rsync_ver}-NEWS" + lasttar_file = f"{dest}/{lastsrcdir}/{rsync_lastver}.tar.gz" + + print(f"""\ +{dash_line} About to: - commit all version changes - - merge the $master_branch branch into the patch/$master_branch/* branches + - merge the {args.master_branch} branch into the patch/{args.master_branch}/* branches - update the files in the "patches" dir and OPTIONALLY (if you type 'y') to launch a shell for each patch +""") + ans = input(" ") -EOT -print " "; -my $ans = ; + s = cmd_run(['git', 'commit', '-a', '-m', f'Preparing for release of {version}']) + if s.returncode: + die('Aborting') -system "git commit -a -m 'Preparing for release of $version'" and exit 1; + print(f'Creating any missing patch branches.') + s = cmd_run(f'packaging/branch-from-patch --branch={args.master_branch} --add-missing') + if s.returncode: + die('Aborting') -print "Updating files in \"patches\" dir ...\n"; -system "packaging/patch-update --branch=$master_branch"; + print('Updating files in "patches" dir ...') + s = cmd_run(f'packaging/patch-update --branch={args.master_branch}') + if s.returncode: + die('Aborting') -if ($ans =~ /^y/i) { - print "\nVisiting all \"patch/$master_branch/*\" branches ...\n"; - system "packaging/patch-update --branch=$master_branch --skip-check --shell"; -} + if re.match(r'^y', ans, re.I): + print(f'\nVisiting all "patch/{args.master_branch}/*" branches ...') + cmd_run(f"packaging/patch-update --branch={args.master_branch} --skip-check --shell") -if (-d 'patches/.git') { - system "cd patches && git commit -a -m 'The patches for $version.'" and exit 1; -} + if os.path.isdir('patches/.git'): + s = cmd_run(f"cd patches && git commit -a -m 'The patches for {version}.'") + if s.returncode: + die('Aborting') -print $break, < "; -$_ = ; - -# We want to use our passphrase-providing "gpg" script, so modify the PATH. -$ENV{PATH} = "$curdir/packaging/bin:$path"; - -my $passphrase; -while (1) { - ReadMode('noecho'); - print "\nEnter your GPG pass-phrase: "; - chomp($passphrase = ); - ReadMode(0); - print "\n"; - - # Briefly create a temp file with the passphrase for git's tagging use. - my $oldmask = umask 077; - unlink($passfile); - open(OUT, '>', $passfile) or die $!; - print OUT $passphrase, "\n"; - close OUT; - umask $oldmask; - $ENV{'GPG_PASSFILE'} = $passfile; - - $_ = `git tag -s -m 'Version $version.' v$version 2>&1`; - print $_; - next if /bad passphrase/; - exit 1 if /failed/; - - if (-d 'patches/.git') { - $_ = `cd patches && git tag -s -m 'Version $version.' v$version 2>&1`; - print $_; - exit 1 if /bad passphrase|failed/; - } - - unlink($passfile); - last; -} - -$ENV{PATH} = $path; - -# Extract the generated files from the old tar. -@_ = @extra_files; -map { s#^#rsync-$lastversion/# } @_; -system "tar xzf $lasttar_file @_"; -rename("rsync-$lastversion", 'a'); - -print "Creating $diff_file ...\n"; -system "$make_gen_cmd && rsync -a @extra_files b/" and exit 1; -my $sed_script = 's:^((---|\+\+\+) [ab]/[^\t]+)\t.*:\1:'; -system "(git diff v$lastversion v$version; diff -upN a b | sed -r '$sed_script') | gzip -9 >$diff_file"; -system "rm -rf a"; -rename('b', "rsync-$version"); - -print "Creating $srctar_file ...\n"; -system "git archive --format=tar --prefix=rsync-$version/ v$version | tar xf -"; -system "support/git-set-file-times --quiet --prefix=rsync-$version/"; -system "fakeroot tar czf $srctar_file rsync-$version; rm -rf rsync-$version"; - -print "Updating files in \"rsync-$version/patches\" dir ...\n"; -mkdir("rsync-$version", 0755); -mkdir("rsync-$version/patches", 0755); -system "packaging/patch-update --skip-check --branch=$master_branch --gen=rsync-$version/patches"; - -print "Creating $pattar_file ...\n"; -system "fakeroot tar chzf $pattar_file rsync-$version/patches; rm -rf rsync-$version"; - -print "Updating the other files in $dest ...\n"; -system "rsync -a README NEWS OLDNEWS TODO $dest"; -unlink($news_file); -link("$dest/NEWS", $news_file); -system "git log --name-status | gzip -9 >$dest/ChangeLog.gz"; - -system "yodl2html -o $dest/rsync.html rsync.yo"; -system "yodl2html -o $dest/rsyncd.conf.html rsyncd.conf.yo"; - -foreach my $fn ($srctar_file, $pattar_file, $diff_file) { - unlink("$fn.asc"); - open(GPG, '|-', "gpg --batch --passphrase-fd=0 -ba $fn") or die $!; - print GPG $passphrase, "\n"; - close GPG; -} - -if (!$pre) { - system "rm $dest/rsync-*.gz $dest/rsync-*.asc $dest/rsync-*-NEWS $dest/src-previews/rsync-*diffs.gz*"; - - foreach my $fn ($srctar_file, "$srctar_file.asc", - $pattar_file, "$pattar_file.asc", - $diff_file, "$diff_file.asc", $news_file) { - (my $top_fn = $fn) =~ s#/src(-\w+)?/#/#; - link($fn, $top_fn); - } -} - -print $break, <<'EOT'; + - update hard-linked top-level release files{skipping} +""") + ans = input(" ") + + # We want to use our passphrase-providing "gpg" script, so modify the PATH. + os.environ['PATH'] = f"{curdir}/packaging/bin:{ORIGINAL_PATH}" + + while True: + passphrase = getpass("\nEnter your GPG pass-phrase: ") + + # Briefly create a temp file with the passphrase for git's tagging use. + oldmask = os.umask(0o077) + if os.path.lexists(passfile): + os.unlink(passfile) + with open(passfile, 'w', encoding='utf-8') as fh: + fh.write(passphrase + "\n") + os.umask(oldmask) + os.environ['GPG_PASSFILE'] = passfile + + out = cmd_txt(f"git tag -s -m 'Version {version}.' {v_ver}")[0] + print(out, end='') + if 'bad passphrase' in out: + continue + if 'failed' in out: + die('Aborting') + + if os.path.isdir('patches/.git'): + out = cmd_txt(f"cd patches && git tag -s -m 'Version {version}.' {v_ver}")[0] + print(out, end='') + if 'bad passphrase' in out or 'failed' in out: + die('Aborting') + + os.unlink(passfile) + break + + os.environ['PATH'] = ORIGINAL_PATH + + # Extract the generated files from the old tar. + tweaked_extra_files = [ f"{rsync_lastver}/{x}" for x in extra_files ] + cmd_chk(['tar', 'xzf', lasttar_file, *tweaked_extra_files]) + os.rename(rsync_lastver, 'a') + + print(f"Creating {diff_file} ...") + for cmd in MAKE_GEN_CMDS: + cmd_chk(cmd) + cmd_chk(['rsync', '-a', *extra_files, 'b/']) + + sed_script = r's:^((---|\+\+\+) [ab]/[^\t]+)\t.*:\1:' # CAUTION: must not contain any single quotes! + cmd_chk(f"(git diff v{lastversion} {v_ver}; diff -upN a b | sed -r '{sed_script}') | gzip -9 >{diff_file}") + shutil.rmtree('a') + os.rename('b', rsync_ver) + + print(f"Creating {srctar_file} ...") + cmd_chk(f"git archive --format=tar --prefix={rsync_ver}/ {v_ver} | tar xf -") + cmd_chk(f"support/git-set-file-times --quiet --prefix={rsync_ver}/") + cmd_chk(['fakeroot', 'tar', 'czf', srctar_file, rsync_ver]) + shutil.rmtree(rsync_ver) + + print(f'Updating files in "{rsync_ver}/patches" dir ...') + os.mkdir(rsync_ver, 0o755) + os.mkdir(f"{rsync_ver}/patches", 0o755) + cmd_chk(f"packaging/patch-update --skip-check --branch={args.master_branch} --gen={rsync_ver}/patches") + + print(f"Creating {pattar_file} ...") + cmd_chk(f"fakeroot tar chzf {pattar_file} {rsync_ver}/patches") + shutil.rmtree(rsync_ver) + + print(f"Updating the other files in {dest} ...") + cmd_chk('rsync -a README NEWS OLDNEWS TODO'.split() + [dest]) + if os.path.lexists(news_file): + os.unlink(news_file) + os.link(f"{dest}/NEWS", news_file) + cmd_chk(f"git log --name-status | gzip -9 >{dest}/ChangeLog.gz") + + cmd_chk(f"yodl2html -o {dest}/rsync.html rsync.yo") + cmd_chk(f"yodl2html -o {dest}/rsyncd.conf.html rsyncd.conf.yo") + + for fn in (srctar_file, pattar_file, diff_file): + asc_fn = fn + '.asc' + if os.path.lexists(asc_fn): + os.unlink(asc_fn) + cmd_chk(['gpg', '--batch', '--passphrase-fd=0', '-ba', fn], input=passphrase) + + if not pre: + for find in f'{dest}/rsync-*.gz {dest}/rsync-*.asc {dest}/rsync-*-NEWS {dest}/src-previews/rsync-*diffs.gz*'.split(): + for fn in glob.glob(find): + os.unlink(fn) + top_link = [ + srctar_file, f"{srctar_file}.asc", + pattar_file, f"{pattar_file}.asc", + diff_file, f"{diff_file}.asc", + news_file, + ] + for fn in top_link: + os.link(fn, re.sub(r'/src(-\w+)?/', '/', fn)) + + print(f"""\ +{dash_line} Local changes are done. When you're satisfied, push the git repository and rsync the release files. Remember to announce the release on *BOTH* rsync-announce@lists.samba.org and rsync@lists.samba.org (and the web)! -EOT - -exit; - -sub get_subprotocol_version -{ - my($subver) = @_; - if ($pre && $proto_changed eq 'changed') { - return $subver == 0 ? 1 : $subver; - } - 0; -} - -sub usage -{ - die < 1: - ts = str(commit_time).rjust(10) - else: - ts = datetime.utcfromtimestamp(commit_time).strftime("%Y-%m-%d %H:%M:%S") - chg = '.' if mtime == commit_time else '*' - print(chg, ts, fn) + print_line(fn, mtime, commit_time) elif mtime != commit_time: if not args.quiet: print(f"Setting {fn}") @@ -62,14 +69,24 @@ def main(): proc.communicate() +def print_line(fn, mtime, commit_time): + if args.list > 1: + ts = str(commit_time).rjust(10) + else: + ts = datetime.utcfromtimestamp(commit_time).strftime("%Y-%m-%d %H:%M:%S") + chg = '.' if mtime == commit_time else '*' + print(chg, ts, fn) + + if __name__ == '__main__': - parser = argparse.ArgumentParser(description="Set the times of the current git checkout to their last-changed time.") + parser = argparse.ArgumentParser(description="Set the times of the current git checkout to their last-changed time.", add_help=False) parser.add_argument('--git-dir', metavar='GIT_DIR', help="The git dir to query (defaults to affecting the current git checkout).") parser.add_argument('--tree', metavar='TREE-ISH', help="The tree-ish to query (defaults to the current branch).") parser.add_argument('--prefix', metavar='PREFIX_STR', help="Prepend the PREFIX_STR to each filename we tweak.") parser.add_argument('--quiet', '-q', action='store_true', help="Don't output the changed-file information.") - parser.add_argument('--list', '-l', action='count', help="List the files and their dates instead of changing them. Repeat for Unix Time instead of human reable.") + parser.add_argument('--list', '-l', action='count', help="List files & times instead of changing them. Repeat for Unix timestamp instead of human readable.") parser.add_argument('files', metavar='FILE', nargs='*', help="Specify a subset of checked-out files to tweak.") + parser.add_argument("--help", "-h", action="help", help="Output this help message and exit.") args = parser.parse_args() main()