summaryrefslogtreecommitdiff
path: root/bin
diff options
context:
space:
mode:
authorOswald Buddenhagen <oswald.buddenhagen@qt.io>2015-11-02 20:58:10 +0100
committerOswald Buddenhagen <oswald.buddenhagen@gmx.de>2020-02-25 13:16:26 +0000
commit84bde271b5351995249cc446522cbf4287339c46 (patch)
tree0abdf7da3098339e616db6e1101ca0c3df891481 /bin
parent7a7ca3d2ba965ddfa32f1aef812afee6a95f33b9 (diff)
downloadqtrepotools-84bde271b5351995249cc446522cbf4287339c46.tar.gz
gpush: carve out some generic code to a perl module
this avoids code duplication when more scripts are added to the suite. Change-Id: I996bb9b8fcbae40a6f3d3c0cd08bd80190a8bbab Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io> Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
Diffstat (limited to 'bin')
-rwxr-xr-xbin/git-gpush259
-rw-r--r--bin/git_gpush.pm309
2 files changed, 324 insertions, 244 deletions
diff --git a/bin/git-gpush b/bin/git-gpush
index e5d48d4..c0dcdbc 100755
--- a/bin/git-gpush
+++ b/bin/git-gpush
@@ -8,14 +8,19 @@
use strict;
use warnings;
-no warnings qw(io);
-use Carp;
-$SIG{__WARN__} = \&Carp::cluck;
-
-use File::Spec;
-use File::Basename;
-use IPC::Open3 qw(open3);
+our ($script, $script_path);
+BEGIN {
+ use Cwd qw(abs_path);
+ if ($^O eq "msys") {
+ $0 =~ s,\\,/,g;
+ $0 =~ s,^(.):/,/$1/,g;
+ }
+ $script_path = $script = abs_path($0);
+ $script_path =~ s,/[^/]+$,,;
+ unshift @INC, $script_path;
+}
+use git_gpush;
# Cannot use Pod::Usage for this file, since git on Windows will invoke its own perl version, which
# may not (msysgit for example) support this module, even if it's considered a Core module.
@@ -100,176 +105,13 @@ License:
EOM
}
-my $debug = 0;
-my $verbose = 0;
-my $quiet = 0;
-my $dry_run = 0;
-
my $remote = "gerrit";
my $ref_from = "HEAD";
my $ref_to = "";
-my %aliases;
-
my @reviewers;
my @CCs;
-my %gitconfig;
-
-sub format_cmd(@)
-{
- return join(' ', map { /\s/ ? '"' . $_ . '"' : $_ } @_);
-}
-
-use constant {
- NUL_STDIN => 0,
- USE_STDIN => 1,
- # FWD_STDIN is not needed
- NUL_STDOUT => 0,
- USE_STDOUT => 4,
- FWD_STDOUT => 8,
- NUL_STDERR => 0,
- # USE_STDERR is not needed
- FWD_STDERR => 32,
- FWD_OUTPUT => 40,
- SILENT_STDIN => 64, # Suppress debug output for stdin
- SOFT_FAIL => 256, # A non-zero exit from the process is not fatal
- DRY_RUN => 512 # Don't actually run the command if $dry_run is true
-};
-
-sub open_process($@)
-{
- my ($flags, @cmd) = @_;
- my %process;
-
- $flags &= ~DRY_RUN if (!$dry_run);
- $process{flags} = $flags;
- if ($flags & DRY_RUN) {
- print "+ ".format_cmd(@cmd)." [DRY]\n" if ($debug);
- return \%process;
- }
- my $cmd = format_cmd(@cmd);
- $process{cmd} = $cmd;
- my ($in, $out, $err);
- if ($flags & USE_STDIN) {
- $in = \$process{stdin};
- } else {
- $in = \'<&NUL';
- }
- if ($flags & USE_STDOUT) {
- $out = \$process{stdout};
- } elsif ($flags & FWD_STDOUT) {
- $out = \'>&STDOUT';
- } else {
- $out = \'>&NUL';
- }
- if ($flags & FWD_STDERR) {
- $err = \'>&STDERR';
- } else {
- $err = \'>&NUL';
- }
- print "+ $cmd\n" if ($debug);
- open(NUL, '>'.File::Spec->devnull()) or die("Failed to open bitbucket: $!\n");
- eval { $process{pid} = open3($$in, $$out, $$err, @cmd); };
- die("Failed to run \"$cmd[0]\": $!\n") if ($@);
- close(NUL);
- return \%process;
-}
-
-sub close_process($)
-{
- my ($process) = @_;
-
- if ($$process{flags} & DRY_RUN) {
- $? = 0;
- return 0;
- }
- my $cmd = $$process{cmd};
- if ($$process{stdout}) {
- close($$process{stdout}) or die("Failed to close read pipe of '$cmd': $!\n");
- }
- waitpid($$process{pid}, 0) or die("Failed to wait for '$cmd': $!\n");
- if ($? & 128) {
- die("'$cmd' crashed with signal ".($? & 127).".\n") if ($? != 141); # allow SIGPIPE
- $? = 0;
- } elsif ($? && !($$process{flags} & SOFT_FAIL)) {
- exit($? >> 8);
- }
- return 0;
-}
-
-# Write any number of lines to the process' stdin.
-# The input is expected to already contain trailing newlines.
-# This function must be called exactly once iff USE_STDIN is used.
-# Note that this will deadlock with USE_STDOUT if the process outputs
-# too much before all input is written.
-sub write_process($@)
-{
- my ($process, @input) = @_;
-
- my $stdin = $$process{stdin};
- my $silent = ($$process{flags} & SILENT_STDIN);
- my $dry = ($$process{flags} & DRY_RUN);
- local $SIG{PIPE} = "IGNORE";
- foreach (@input) {
- print "> $_" if ($debug && !$silent);
- print $stdin $_ if (!$dry);
- }
- $dry or close($stdin) or die("Failed to close write pipe of '$$process{cmd}': $!\n");
-}
-
-# Read a line from the process' stdout.
-sub read_process($)
-{
- my ($process) = @_;
-
- my $fh = $$process{stdout};
- $_ = <$fh>;
- if (defined($_)) {
- chomp;
- print "- $_\n" if ($debug);
- }
- return $_;
-}
-
-# Read any number of null-terminated fields from the process' stdout.
-sub read_fields($@)
-{
- my $process = shift;
- my $fh = $$process{stdout};
- return 0 if (eof($fh));
- local $/ = "\0";
- for (@_) { chop($_ = <$fh>); }
- return 1;
-}
-
-# The equivalent of system().
-sub run_process($@)
-{
- my ($flags, @cmd) = @_;
-
- close_process(open_process($flags, @cmd));
-}
-
-# The equivalent of popen("r").
-sub open_cmd_pipe($@)
-{
- my ($flags, @cmd) = @_;
-
- return open_process(USE_STDOUT | FWD_STDERR | $flags, @cmd);
-}
-
-# Run the specified command and try to read exactly one line from its stdout.
-sub read_cmd_line($@)
-{
- my ($flags, @cmd) = @_;
-
- my $proc = open_cmd_pipe($flags, @cmd);
- read_process($proc);
- close_process($proc);
- return $_;
-}
-
sub parse_arguments(@)
{
while (scalar @_) {
@@ -326,76 +168,11 @@ sub parse_arguments(@)
if ($quiet && $verbose);
}
-sub fileContents($)
-{
- my ($filename) = @_;
-
- my @contents = "";
- my $fh;
- if (-e $filename && open($fh, "< $filename")) {
- @contents = <$fh>;
- close $fh;
- }
- return @contents;
-}
-
-sub git_configs($)
-{
- my ($key) = @_;
- my $ref = $gitconfig{$key};
- return defined($ref) ? @$ref : ();
-}
-
-sub git_config($;$)
+sub process_config()
{
- my ($key, $dflt) = @_;
- my @cfg = git_configs($key);
- return scalar(@cfg) ? $cfg[-1] : $dflt;
-}
-
-sub load_config()
-{
- my $script_path = dirname($0);
-
- # Read aliases from .git-gpush-aliases file
- my $in_aliases = 1;
- foreach my $line (fileContents("$script_path/.git-gpush-aliases")) {
- chomp $line;
- $line =~ s,(#|//).*$,,; # Remove any comments
- if ($line =~ /^\[([^]]+)\]/) {
- if ($1 eq "aliases") {
- $in_aliases = 1;
- } elsif ($1 eq "config") {
- $in_aliases = 0;
- } else {
- die("Unrecognized section '$1' in alias file.\n");
- }
- } elsif ($line =~ /^\s*([^ =]+)\s*=\s*(.*?)\s*$/) { # Capture the value
- if ($in_aliases) {
- for my $alias (split(/,/, $1)) {
- $aliases{$alias} = $2;
- }
- } else {
- push @{$gitconfig{"gpush.$1"}}, $2;
- }
- }
- }
-
- # Read all git configuration at once, as that's faster than repeated
- # git invocations, especially under Windows.
- my $cfg = open_cmd_pipe(0, 'git', 'config', '-l', '-z');
- while (read_fields($cfg, my $entry)) {
- $entry =~ /^([^\n]+)\n(.*)$/;
- push @{$gitconfig{$1}}, $2;
- }
- close_process($cfg);
+ load_config();
$remote = git_config('gpush.remote', $remote);
- foreach (keys %gitconfig) {
- if (/^gpush\.alias\.(.*)$/) {
- $aliases{$1} = git_config($_);
- }
- }
}
sub lookup_alias($)
@@ -411,12 +188,6 @@ sub lookup_alias($)
return $user;
}
-sub goto_gitdir()
-{
- my $cdup = read_cmd_line(0, 'git', 'rev-parse', '--show-cdup');
- chdir($cdup) unless $cdup eq "";
-}
-
# Find _the_ branch the specified commit lives on. This can be the current
# branch (and other branches are ignored), or _one_ other branch.
sub determine_branch($)
@@ -498,7 +269,7 @@ sub push_patches()
run_process(FWD_OUTPUT, @gitcmd);
}
-load_config();
+process_config();
parse_arguments(@ARGV);
goto_gitdir();
determine_target();
diff --git a/bin/git_gpush.pm b/bin/git_gpush.pm
new file mode 100644
index 0000000..0d0b85b
--- /dev/null
+++ b/bin/git_gpush.pm
@@ -0,0 +1,309 @@
+# Copyright (C) 2017 The Qt Company Ltd.
+# Contact: http://www.qt.io/licensing/
+#
+# You may use this file under the terms of the 3-clause BSD license.
+# See the file LICENSE from this package for details.
+#
+
+package git_gpush;
+
+use strict;
+use warnings;
+no warnings qw(io);
+
+use Carp;
+$SIG{__WARN__} = \&Carp::cluck;
+
+use File::Spec;
+use IPC::Open3 qw(open3);
+
+our @_imported;
+BEGIN {
+ no strict 'refs';
+ @_imported = keys %{__PACKAGE__.'::'};
+}
+
+##################
+# shared options #
+##################
+
+our $debug = 0;
+our $verbose = 0;
+our $quiet = 0;
+our $dry_run = 0;
+
+#######################
+# subprocess handling #
+#######################
+
+use constant {
+ NUL_STDIN => 0,
+ USE_STDIN => 1,
+ # FWD_STDIN is not needed
+ NUL_STDOUT => 0,
+ USE_STDOUT => 4,
+ FWD_STDOUT => 8,
+ NUL_STDERR => 0,
+ # USE_STDERR is not needed
+ FWD_STDERR => 32,
+ FWD_OUTPUT => 40,
+ SILENT_STDIN => 64, # Suppress debug output for stdin
+ SOFT_FAIL => 256, # A non-zero exit from the process is not fatal
+ DRY_RUN => 512 # Don't actually run the command if $dry_run is true
+};
+
+sub _format_cmd(@)
+{
+ return join(' ', map { /\s/ ? '"' . $_ . '"' : $_ } @_);
+}
+
+sub open_process($@)
+{
+ my ($flags, @cmd) = @_;
+ my %process;
+
+ $flags &= ~DRY_RUN if (!$dry_run);
+ $process{flags} = $flags;
+ if ($flags & DRY_RUN) {
+ print "+ "._format_cmd(@cmd)." [DRY]\n" if ($debug);
+ return \%process;
+ }
+ my $cmd = _format_cmd(@cmd);
+ $process{cmd} = $cmd;
+ my ($in, $out, $err);
+ if ($flags & USE_STDIN) {
+ $in = \$process{stdin};
+ } else {
+ $in = \'<&NUL';
+ }
+ if ($flags & USE_STDOUT) {
+ $out = \$process{stdout};
+ } elsif ($flags & FWD_STDOUT) {
+ $out = \'>&STDOUT';
+ } else {
+ $out = \'>&NUL';
+ }
+ if ($flags & FWD_STDERR) {
+ $err = \'>&STDERR';
+ } else {
+ $err = \'>&NUL';
+ }
+ print "+ $cmd\n" if ($debug);
+ open(NUL, '>'.File::Spec->devnull()) or die("Failed to open bitbucket: $!\n");
+ eval { $process{pid} = open3($$in, $$out, $$err, @cmd); };
+ die("Failed to run \"$cmd[0]\": $!\n") if ($@);
+ close(NUL);
+ return \%process;
+}
+
+sub close_process($)
+{
+ my ($process) = @_;
+
+ if ($$process{flags} & DRY_RUN) {
+ $? = 0;
+ return 0;
+ }
+ my $cmd = $$process{cmd};
+ if ($$process{stdout}) {
+ close($$process{stdout}) or die("Failed to close read pipe of '$cmd': $!\n");
+ }
+ waitpid($$process{pid}, 0) or die("Failed to wait for '$cmd': $!\n");
+ if ($? & 128) {
+ die("'$cmd' crashed with signal ".($? & 127).".\n") if ($? != 141); # allow SIGPIPE
+ $? = 0;
+ } elsif ($? && !($$process{flags} & SOFT_FAIL)) {
+ exit($? >> 8);
+ }
+ return 0;
+}
+
+# Write any number of lines to the process' stdin.
+# The input is expected to already contain trailing newlines.
+# This function must be called exactly once iff USE_STDIN is used.
+# Note that this will deadlock with USE_STDOUT if the process outputs
+# too much before all input is written.
+sub write_process($@)
+{
+ my ($process, @input) = @_;
+
+ my $stdin = $$process{stdin};
+ my $silent = ($$process{flags} & SILENT_STDIN);
+ my $dry = ($$process{flags} & DRY_RUN);
+ local $SIG{PIPE} = "IGNORE";
+ foreach (@input) {
+ print "> $_" if ($debug && !$silent);
+ print $stdin $_ if (!$dry);
+ }
+ $dry or close($stdin) or die("Failed to close write pipe of '$$process{cmd}': $!\n");
+}
+
+# Read a line from the process' stdout.
+sub read_process($)
+{
+ my ($process) = @_;
+
+ my $fh = $$process{stdout};
+ $_ = <$fh>;
+ if (defined($_)) {
+ chomp;
+ print "- $_\n" if ($debug);
+ }
+ return $_;
+}
+
+# Read any number of null-terminated fields from the process' stdout.
+sub read_fields($@)
+{
+ my $process = shift;
+ my $fh = $$process{stdout};
+ return 0 if (eof($fh));
+ local $/ = "\0";
+ for (@_) { chop($_ = <$fh>); }
+ return 1;
+}
+
+# The equivalent of system().
+sub run_process($@)
+{
+ my ($flags, @cmd) = @_;
+
+ close_process(open_process($flags, @cmd));
+}
+
+# The equivalent of popen("r").
+sub open_cmd_pipe($@)
+{
+ my ($flags, @cmd) = @_;
+
+ return open_process(USE_STDOUT | FWD_STDERR | $flags, @cmd);
+}
+
+# Run the specified command and try to read exactly one line from its stdout.
+sub read_cmd_line($@)
+{
+ my ($flags, @cmd) = @_;
+
+ my $proc = open_cmd_pipe($flags, @cmd);
+ read_process($proc);
+ close_process($proc);
+ return $_;
+}
+
+##############
+# git basics #
+##############
+
+our $gitdir; # $GIT_DIR
+
+sub goto_gitdir()
+{
+ my $cdup = read_cmd_line(0, 'git', 'rev-parse', '--show-cdup');
+ die("fatal: This operation must be run in a work tree\n") if (!defined($cdup));
+ chdir($cdup) unless ($cdup eq "");
+ $gitdir = read_cmd_line(0, 'git', 'rev-parse', '--git-dir');
+}
+
+# `git config --list` output, plus contents of .git-gpush-aliases' [config]
+our %gitconfig; # { key => [ value, ... ] }
+
+sub _load_git_config()
+{
+ # Read all git configuration at once, as that's faster than repeated
+ # git invocations, especially under Windows.
+ my $cfg = open_cmd_pipe(0, 'git', 'config', '-l', '-z');
+ while (read_fields($cfg, my $entry)) {
+ $entry =~ /^([^\n]+)\n(.*)$/;
+ push @{$gitconfig{$1}}, $2;
+ }
+ close_process($cfg);
+}
+
+sub git_configs($)
+{
+ my ($key) = @_;
+ my $ref = $gitconfig{$key};
+ return defined($ref) ? @$ref : ();
+}
+
+sub git_config($;$)
+{
+ my ($key, $dflt) = @_;
+ my @cfg = git_configs($key);
+ return scalar(@cfg) ? $cfg[-1] : $dflt;
+}
+
+#################
+# configuration #
+#################
+
+sub _file_contents($)
+{
+ my ($filename) = @_;
+
+ my @contents = "";
+ my $fh;
+ if (-e $filename && open($fh, "< $filename")) {
+ @contents = <$fh>;
+ close $fh;
+ }
+ return @contents;
+}
+
+our %aliases; # { alias => login }
+
+sub load_config()
+{
+ # Read config from .git-gpush-aliases file
+ my $in_aliases = 1;
+ foreach my $line (_file_contents($::script_path."/.git-gpush-aliases")) {
+ chomp $line;
+ $line =~ s,(#|//).*$,,; # Remove any comments
+ if ($line =~ /^\[([^]]+)\]/) {
+ if ($1 eq "aliases") {
+ $in_aliases = 1;
+ } elsif ($1 eq "config") {
+ $in_aliases = 0;
+ } else {
+ die("Unrecognized section '$1' in alias file.\n");
+ }
+ } elsif ($line =~ /^\s*([^ =]+)\s*=\s*(.*?)\s*$/) { # Capture the value
+ if ($in_aliases) {
+ for my $alias (split(/,/, $1)) {
+ $aliases{$alias} = $2;
+ }
+ } else {
+ push @{$gitconfig{"gpush.$1"}}, $2;
+ }
+ }
+ }
+
+ _load_git_config();
+
+ foreach (keys %gitconfig) {
+ if (/^gpush\.alias\.(.*)$/) {
+ $aliases{$1} = git_config($_);
+ }
+ }
+}
+
+#############################
+# export all public symbols #
+#############################
+
+sub import()
+{
+ no strict 'refs';
+
+ my %imported = map { $_ => 1 } @_imported;
+ undef @_imported;
+ while (my ($name, $symbol) = each %{__PACKAGE__.'::'}) {
+ next if (defined($imported{$name}));
+ next if ($name =~ /^(_.*|BEGIN|END|a|b|import)$/);
+ # $symbol values referring to constants are resolved, but we want to alias the
+ # inline function. Other values are typeglobs which can be aliased directly.
+ *{caller.'::'.$name} = !length(ref($symbol)) ? *$symbol : \&{__PACKAGE__.'::'.$name};
+ }
+}
+
+1;