{"thread":{"id":"7325","subject":"[PATCH] contrib/continuous: a continuous integration build manager","startedAt":"2007-03-20T04:33:41Z","lastAt":"2007-03-21T02:27:42Z","messageCount":2,"participants":["Shawn O. Pearce","Bill Lear"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"37582","messageId":"20070320043341.GA29436@spearce.org","threadId":"7325","inReplyTo":null,"subject":"[PATCH] contrib/continuous: a continuous integration build manager","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2007-03-20T04:33:41Z","receivedAt":"2007-03-20T04:33:41Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This is a simple but powerful continuous integration build system\nfor Git.  It works by receiving push events from repositories\nthrough the post-receive hook, aggregates them on a per-branch\nbasis into a first-come-first-serve build queue, and lets a\nbackground build daemon perform builds one at a time.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n\n I found this useful.  It was a quick hack I did this weekend.\n I may grow it out further and try to create a simple tinderbox-like\n webpage to show prior build logs, and hyperlink into gitweb.cgi.\n Or not.  ;-)\n\n I'm using it to drive builds on a Solaris 9 system, and (gah)\n forward the failures to the gulity parties by way of the local\n Microsoft Exchange Server.  Hence there are some ugly bits about\n subclassing Net::SMTP.\n\n contrib/continuous/cidaemon              |  503 ++++++++++++++++++++++++++++++\n contrib/continuous/post-receive-cinotify |  104 ++++++\n 2 files changed, 607 insertions(+), 0 deletions(-)\n create mode 100644 contrib/continuous/cidaemon\n create mode 100644 contrib/continuous/post-receive-cinotify\n\ndiff --git a/contrib/continuous/cidaemon b/contrib/continuous/cidaemon\nnew file mode 100644\nindex 0000000..4009a15\n--- /dev/null\n+++ b/contrib/continuous/cidaemon\n@@ -0,0 +1,503 @@\n+#!/usr/bin/perl\n+#\n+# A daemon that waits for update events sent by its companion\n+# post-receive-cinotify hook, checks out a new copy of source,\n+# compiles it, and emails the guilty parties if the compile\n+# (and optionally test suite) fails.\n+#\n+# To use this daemon, configure it and run it.  It will disconnect\n+# from your terminal and fork into the background.  The daemon must\n+# have local filesystem access to the source repositories, as it\n+# uses objects/info/alternates to avoid copying objects.\n+#\n+# Add its companion post-receive-cinotify hook as the post-receive\n+# hook to each repository that the daemon should monitor.  Yes, a\n+# single daemon can monitor more than one repository.\n+#\n+# To use multiple daemons on the same system, give them each a\n+# unique queue file and tmpdir.\n+#\n+# Global Config\n+# -------------\n+# Reads from a Git style configuration file.  This will be\n+# ~/.gitconfig by default but can be overridden by setting\n+# the GIT_CONFIG_FILE environment variable before starting.\n+#\n+# cidaemon.smtpHost\n+#   Hostname of the SMTP server the daemon will send email\n+#   through.  Defaults to 'localhost'.\n+#\n+# cidaemon.smtpUser\n+#   Username to authenticate to the SMTP server as.  This\n+#   variable is optional; if it is not supplied then no\n+#   authentication will be performed.\n+#\n+# cidaemon.smtpPassword\n+#   Password to authenticate to the SMTP server as.  This\n+#   variable is optional.  If not supplied but smtpUser was,\n+#   the daemon prompts for the password before forking into\n+#   the background.\n+#\n+# cidaemon.smtpAuth\n+#   Type of authentication to perform with the SMTP server.\n+#   If set to 'login' and smtpUser was defined, this will\n+#   use the AUTH LOGIN command, which is suitable for use\n+#   with at least one version of Microsoft Exchange Server.\n+#   If not set the daemon will use whatever auth methods\n+#   are supported by your version of Net::SMTP.\n+#\n+# cidaemon.email\n+#   Email address that daemon generated emails will be sent\n+#   from.  This should be a useful email address within your\n+#   organization.  Required.\n+#\n+# cidaemon.name\n+#   Human friendly name that the daemon will send emails as.\n+#   Defaults to 'cidaemon'.\n+#\n+# cidaemon.scanDelay\n+#   Number of seconds to sleep between polls of the queue file.\n+#   Defaults to 60.\n+#\n+# cidaemon.recentCache\n+#   Number of recent commit SHA-1s per repository to cache and\n+#   skip building if they appear again.  This is useful to avoid\n+#   rebuilding the same commit multiple times just because it was\n+#   pushed into more than one branch.  Defaults to 100.\n+#\n+# cidaemon.tmpdir\n+#   Scratch directory to create the builds within.  The daemon\n+#   makes a new subdirectory for each build, then deletes it when\n+#   the build has finished.  The pid file is also placed here.\n+#   Defaults to '/tmp'.\n+#\n+# cidaemon.queue\n+#   Path to the queue file that the post-receive-cinotify hook\n+#   appends events to.  This file is polled by the daemon.  It\n+#   must not be on an NFS mount (uses flock).  Required.\n+#\n+# cidaemon.nocc\n+#   Perl regex patterns to match against author and committer\n+#   lines.  If a pattern matches, that author or committer will\n+#   not be notified of a build failure.\n+#\n+# Per Repository Config\n+# ----------------------\n+# Read from the source repository's config file.\n+#\n+# builder.command\n+#   Shell command to execute the build.  This command must\n+#   return 0 on \"success\" and non-zero on failure.  If you\n+#   also want to run a test suite, make sure your command\n+#   does that too.  Required.\n+#\n+# builder.queue\n+#   Queue file to notify the cidaemon through.  Should match\n+#   cidaemon.queue.  If not set the hook will not notify the\n+#   cidaemon.\n+#\n+# builder.skip\n+#   Perl regex patterns of refs that should not be sent to\n+#   cidaemon.  Updates of these refs will be ignored.\n+#\n+# builder.newBranchBase\n+#   Glob patterns of refs that should be used to form the\n+#   'old' revions of a newly created ref.  This should set\n+#   to be globs that match your 'mainline' branches.  This\n+#   way a build failure of a brand new topic branch does not\n+#   attempt to email everyone since the beginning of time;\n+#   instead it only emails those authors of commits not in\n+#   these 'mainline' branches.\n+\n+local $ENV{PATH} = join ':', qw(\n+\t/opt/git/bin\n+\t/usr/bin\n+\t/bin\n+\t);\n+\n+use strict;\n+use warnings;\n+use FindBin qw($RealBin);\n+use File::Spec;\n+use lib File::Spec->catfile($RealBin, '..', 'perl5');\n+use Storable qw(retrieve nstore);\n+use Fcntl ':flock';\n+use POSIX qw(strftime);\n+use Getopt::Long qw(:config no_auto_abbrev auto_help);\n+\n+sub git_config ($;$)\n+{\n+\tmy $var = shift;\n+\tmy $required = shift || 0;\n+\tlocal *GIT;\n+\topen GIT, '-|','git','config','--get',$var;\n+\tmy $r = <GIT>;\n+\tchop $r if $r;\n+\tclose GIT;\n+\tdie \"error: $var not set.\\n\" if ($required && !$r);\n+\treturn $r;\n+}\n+\n+package EXCHANGE_NET_SMTP;\n+\n+# Microsoft Exchange Server requires an 'AUTH LOGIN'\n+# style of authentication.  This is different from\n+# the default supported by Net::SMTP so we subclass\n+# and override the auth method to support that.\n+\n+use Net::SMTP;\n+use Net::Cmd;\n+use MIME::Base64 qw(encode_base64);\n+our @ISA = qw(Net::SMTP);\n+our $auth_type = ::git_config 'cidaemon.smtpAuth';\n+\n+sub new\n+{\n+\tmy $self = shift;\n+\tmy $type = ref($self) || $self;\n+\t$type->SUPER::new(@_);\n+}\n+\n+sub auth\n+{\n+\tmy $self = shift;\n+\treturn $self->SUPER::auth(@_) unless $auth_type eq 'login';\n+\n+\tmy $user = encode_base64 shift, '';\n+\tmy $pass = encode_base64 shift, '';\n+\treturn 0 unless CMD_MORE == $self->command(\"AUTH LOGIN\")->response;\n+\treturn 0 unless CMD_MORE == $self->command($user)->response;\n+\tCMD_OK == $self->command($pass)->response;\n+}\n+\n+package main;\n+\n+my ($debug_flag, %recent);\n+\n+my $ex_host = git_config('cidaemon.smtpHost') || 'localhost';\n+my $ex_user = git_config('cidaemon.smtpUser');\n+my $ex_pass = git_config('cidaemon.smtpPassword');\n+\n+my $ex_from_addr = git_config('cidaemon.email', 1);\n+my $ex_from_name = git_config('cidaemon.name') || 'cidaemon';\n+\n+my $scan_delay = git_config('cidaemon.scanDelay') || 60;\n+my $recent_size = git_config('cidaemon.recentCache') || 100;\n+my $tmpdir = git_config('cidaemon.tmpdir') || '/tmp';\n+my $queue_name = git_config('cidaemon.queue', 1);\n+my $queue_lock = \"$queue_name.lock\";\n+\n+my @nocc_list;\n+open GIT,'git config --get-all cidaemon.nocc|';\n+while (<GIT>) {\n+\tchop;\n+\tpush @nocc_list, $_;\n+}\n+close GIT;\n+\n+sub nocc_author ($)\n+{\n+\tlocal $_ = shift;\n+\tforeach my $pat (@nocc_list) {\n+\t\treturn 1 if /$pat/;\n+\t}\n+\t0;\n+}\n+\n+sub input_echo ($)\n+{\n+\tmy $prompt = shift;\n+\n+\tlocal $| = 1;\n+\tprint $prompt;\n+\tmy $input = <STDIN>;\n+\tchop $input;\n+\treturn $input;\n+}\n+\n+sub input_noecho ($)\n+{\n+\tmy $prompt = shift;\n+\n+\tmy $end = sub {system('stty','echo');print \"\\n\";exit};\n+\tlocal $SIG{TERM} = $end;\n+\tlocal $SIG{INT} = $end;\n+\tsystem('stty','-echo');\n+\n+\tlocal $| = 1;\n+\tprint $prompt;\n+\tmy $input = <STDIN>;\n+\tsystem('stty','echo');\n+\tprint \"\\n\";\n+\tchop $input;\n+\treturn $input;\n+}\n+\n+sub rfc2822_date ()\n+{\n+\t strftime(\"%a, %d %b %Y %H:%M:%S %Z\", localtime);\n+}\n+\n+sub send_email ($$$)\n+{\n+\tmy ($subj, $body, $to) = @_;\n+\tmy $now = rfc2822_date;\n+\tmy $to_str = '';\n+\tmy @rcpt_to;\n+\tforeach (@$to) {\n+\t\tmy $s = $_;\n+\t\t$s =~ s/^/\"/;\n+\t\t$s =~ s/(\\s+<)/\"$1/;\n+\t\t$to_str .= ', ' if $to_str;\n+\t\t$to_str .= $s;\n+\t\tpush @rcpt_to, $1 if $s =~ /<(.*)>/;\n+\t}\n+\tdie \"Nobody to send to.\\n\" unless @rcpt_to;\n+\tmy $msg = <<EOF;\n+From: \"$ex_from_name\" <$ex_from_addr>\n+To: $to_str\n+Date: $now\n+Subject: $subj\n+\n+$body\n+EOF\n+\n+\tmy $smtp = EXCHANGE_NET_SMTP->new(Host => $ex_host)\n+\t\tor die \"Cannot connect to $ex_host: $!\\n\";\n+\tif ($ex_user && $ex_pass) {\n+\t\t$smtp->auth($ex_user,$ex_pass)\n+\t\t\tor die \"$ex_host rejected $ex_user\\n\";\n+\t}\n+\t$smtp->mail($ex_from_addr)\n+\t\tor die \"$ex_host rejected $ex_from_addr\\n\";\n+\tscalar($smtp->recipient(@rcpt_to, { SkipBad => 1 }))\n+\t\tor die \"$ex_host did not accept any addresses.\\n\";\n+\t$smtp->data($msg)\n+\t\tor die \"$ex_host rejected message data\\n\";\n+\t$smtp->quit;\n+}\n+\n+sub pop_queue ()\n+{\n+\topen LOCK, \">$queue_lock\" or die \"Can't open $queue_lock: $!\";\n+\tflock LOCK, LOCK_EX;\n+\n+\tmy $queue = -f $queue_name ? retrieve $queue_name : [];\n+\tmy $ent = shift @$queue;\n+\tnstore $queue, $queue_name;\n+\n+\tflock LOCK, LOCK_UN;\n+\tclose LOCK;\n+\t$ent;\n+}\n+\n+sub git_exec (@)\n+{\n+\tsystem('git',@_) == 0 or die \"Cannot git \" . join(' ', @_) . \"\\n\";\n+}\n+\n+sub git_val (@)\n+{\n+\topen(C, '-|','git',@_);\n+\tmy $r = <C>;\n+\tchop $r if $r;\n+\tclose C;\n+\t$r;\n+}\n+\n+sub do_build ($$)\n+{\n+\tmy ($git_dir, $new) = @_;\n+\n+\tmy $tmp = File::Spec->catfile($tmpdir, \"builder$$\");\n+\tsystem('rm','-rf',$tmp) == 0 or die \"Cannot clear $tmp\\n\";\n+\tdie \"Cannot clear $tmp.\\n\" if -e $tmp;\n+\n+\tmy $result = 1;\n+\teval {\n+\t\tmy $command;\n+\t\t{\n+\t\t\tlocal $ENV{GIT_DIR} = $git_dir;\n+\t\t\t$command = git_val 'config','builder.command';\n+\t\t}\n+\t\tdie \"No builder.command for $git_dir.\\n\" unless $command;\n+\n+\t\tgit_exec 'clone','-n','-l','-s',$git_dir,$tmp;\n+\t\tchmod 0700, $tmp or die \"Cannot lock $tmp\\n\";\n+\t\tchdir $tmp or die \"Cannot enter $tmp\\n\";\n+\n+\t\tgit_exec 'update-ref','HEAD',$new;\n+\t\tgit_exec 'read-tree','-m','-u','HEAD','HEAD';\n+\t\tsystem $command;\n+\t\tif ($? == -1) {\n+\t\t\tprint STDERR \"failed to execute '$command': $!\\n\";\n+\t\t\t$result = 1;\n+\t\t} elsif ($? & 127) {\n+\t\t\tmy $sig = $? & 127;\n+\t\t\tprint STDERR \"'$command' died from signal $sig\\n\";\n+\t\t\t$result = 1;\n+\t\t} else {\n+\t\t\tmy $r = $? >> 8;\n+\t\t\tprint STDERR \"'$command' exited with $r\\n\" if $r;\n+\t\t\t$result = $r;\n+\t\t}\n+\t};\n+\tif ($@) {\n+\t\t$result = 2;\n+\t\tprint STDERR \"$@\\n\";\n+\t}\n+\n+\tchdir '/';\n+\tsystem('rm','-rf',$tmp);\n+\trmdir $tmp;\n+\t$result;\n+}\n+\n+sub build_failed ($$$$$)\n+{\n+\tmy ($git_dir, $ref, $old, $new, $msg) = @_;\n+\n+\t$git_dir =~ m,/([^/]+)$,;\n+\tmy $repo_name = $1;\n+\t$ref =~ s,^refs/(heads|tags)/,,;\n+\n+\tmy %authors;\n+\tmy $shortlog;\n+\tmy $revstr;\n+\t{\n+\t\tlocal $ENV{GIT_DIR} = $git_dir;\n+\t\tmy @revs = ($new);\n+\t\tpush @revs, '--not', @$old if @$old;\n+\t\topen LOG,'-|','git','rev-list','--pretty=raw',@revs;\n+\t\twhile (<LOG>) {\n+\t\t\tif (s/^(author|committer) //) {\n+\t\t\t\tchomp;\n+\t\t\t\ts/>.*$/>/;\n+\t\t\t\t$authors{$_} = 1 unless nocc_author $_;\n+\t\t\t}\n+\t\t}\n+\t\tclose LOG;\n+\t\topen LOG,'-|','git','shortlog',@revs;\n+\t\t$shortlog .= $_ while <LOG>;\n+\t\tclose LOG;\n+\t\t$revstr = join(' ', @revs);\n+\t}\n+\n+\tmy @to = sort keys %authors;\n+\tunless (@to) {\n+\t\tprint STDERR \"error: No authors in $revstr\\n\";\n+\t\treturn;\n+\t}\n+\n+\tmy $subject = \"[$repo_name] $ref : Build Failed\";\n+\tmy $body = <<EOF;\n+Project: $git_dir\n+Branch:  $ref\n+Commits: $revstr\n+\n+$shortlog\n+Build Output:\n+--------------------------------------------------------------\n+$msg\n+EOF\n+\tsend_email($subject, $body, \\@to);\n+}\n+\n+sub run_build ($$$$)\n+{\n+\tmy ($git_dir, $ref, $old, $new) = @_;\n+\n+\tif ($debug_flag) {\n+\t\tmy @revs = ($new);\n+\t\tpush @revs, '--not', @$old if @$old;\n+\t\tprint \"BUILDING $git_dir\\n\";\n+\t\tprint \"  BRANCH: $ref\\n\";\n+\t\tprint \"  COMMITS: \", join(' ', @revs), \"\\n\";\n+\t}\n+\n+\tlocal(*R, *W);\n+\tpipe R, W or die \"cannot pipe builder: $!\";\n+\n+\tmy $builder = fork();\n+\tif (!defined $builder) {\n+\t\tdie \"cannot fork builder: $!\";\n+\t} elsif (0 == $builder) {\n+\t\tclose R;\n+\t\tclose STDIN;open(STDIN, '/dev/null');\n+\t\topen(STDOUT, '>&W');\n+\t\topen(STDERR, '>&W');\n+\t\texit do_build $git_dir, $new;\n+\t} else {\n+\t\tclose W;\n+\t\tmy $out = '';\n+\t\t$out .= $_ while <R>;\n+\t\tclose R;\n+\t\twaitpid $builder, 0;\n+\t\tbuild_failed $git_dir, $ref, $old, $new, $out if $?;\n+\t}\n+\n+\tprint \"DONE\\n\\n\" if $debug_flag;\n+}\n+\n+sub daemon_loop ()\n+{\n+\tmy $run = 1;\n+\tmy $stop_sub = sub {$run = 0};\n+\t$SIG{HUP} = $stop_sub;\n+\t$SIG{INT} = $stop_sub;\n+\t$SIG{TERM} = $stop_sub;\n+\n+\tmkdir $tmpdir, 0755;\n+\tmy $pidfile = File::Spec->catfile($tmpdir, \"cidaemon.pid\");\n+\topen(O, \">$pidfile\"); print O \"$$\\n\"; close O;\n+\n+\twhile ($run) {\n+\t\tmy $ent = pop_queue;\n+\t\tif ($ent) {\n+\t\t\tmy ($git_dir, $ref, $old, $new) = @$ent;\n+\n+\t\t\t$ent = $recent{$git_dir};\n+\t\t\t$recent{$git_dir} = $ent = [[], {}] unless $ent;\n+\t\t\tmy ($rec_arr, $rec_hash) = @$ent;\n+\t\t\tnext if $rec_hash->{$new}++;\n+\t\t\twhile (@$rec_arr >= $recent_size) {\n+\t\t\t\tmy $to_kill = shift @$rec_arr;\n+\t\t\t\tdelete $rec_hash->{$to_kill};\n+\t\t\t}\n+\t\t\tpush @$rec_arr, $new;\n+\n+\t\t\trun_build $git_dir, $ref, $old, $new;\n+\t\t} else {\n+\t\t\tsleep $scan_delay;\n+\t\t}\n+\t}\n+\n+\tunlink $pidfile;\n+}\n+\n+$debug_flag = 0;\n+GetOptions(\n+\t'debug|d' => \\$debug_flag,\n+\t'smtp-user=s' => \\$ex_user,\n+) or die \"usage: $0 [--debug] [--smtp-user=user]\\n\";\n+\n+$ex_pass = input_noecho(\"$ex_user SMTP password: \")\n+\tif ($ex_user && !$ex_pass);\n+\n+if ($debug_flag) {\n+\tdaemon_loop;\n+\texit 0;\n+}\n+\n+my $daemon = fork();\n+if (!defined $daemon) {\n+\tdie \"cannot fork daemon: $!\";\n+} elsif (0 == $daemon) {\n+\tclose STDIN;open(STDIN, '/dev/null');\n+\tclose STDOUT;open(STDOUT, '>/dev/null');\n+\tclose STDERR;open(STDERR, '>/dev/null');\n+\tdaemon_loop;\n+\texit 0;\n+} else {\n+\tprint \"Daemon $daemon running in the background.\\n\";\n+}\ndiff --git a/contrib/continuous/post-receive-cinotify b/contrib/continuous/post-receive-cinotify\nnew file mode 100644\nindex 0000000..b8f5a60\n--- /dev/null\n+++ b/contrib/continuous/post-receive-cinotify\n@@ -0,0 +1,104 @@\n+#!/usr/bin/perl\n+#\n+# A hook that notifies its companion cidaemon through a simple\n+# queue file that a ref has been updated via a push (actually\n+# by a receive-pack running on the server).\n+#\n+# See cidaemon for per-repository configuration details.\n+#\n+# To use this hook, add it as the post-receive hook, make it\n+# executable, and set its configuration options.\n+#\n+\n+local $ENV{PATH} = '/opt/git/bin';\n+\n+use strict;\n+use warnings;\n+use File::Spec;\n+use Storable qw(retrieve nstore);\n+use Fcntl ':flock';\n+\n+my $git_dir = File::Spec->rel2abs($ENV{GIT_DIR});\n+my $queue_name = `git config --get builder.queue`;chop $queue_name;\n+$queue_name =~ m,^([^\\s]+)$,; $queue_name = $1; # untaint\n+unless ($queue_name) {\n+\t1 while <STDIN>;\n+\tprint STDERR \"\\nerror: builder.queue not set.  Not enqueing.\\n\\n\";\n+\texit;\n+}\n+my $queue_lock = \"$queue_name.lock\";\n+\n+my @skip;\n+open S, \"git config --get-all builder.skip|\";\n+while (<S>) {\n+\tchop;\n+\tpush @skip, $_;\n+}\n+close S;\n+\n+my @new_branch_base;\n+open S, \"git config --get-all builder.newBranchBase|\";\n+while (<S>) {\n+\tchop;\n+\tpush @new_branch_base, $_;\n+}\n+close S;\n+\n+sub skip ($)\n+{\n+\tlocal $_ = shift;\n+\tforeach my $p (@skip) {\n+\t\treturn 1 if /^$p/;\n+\t}\n+\t0;\n+}\n+\n+open LOCK, \">$queue_lock\" or die \"Can't open $queue_lock: $!\";\n+flock LOCK, LOCK_EX;\n+\n+my $queue = -f $queue_name ? retrieve $queue_name : [];\n+my %existing;\n+foreach my $r (@$queue) {\n+\tmy ($gd, $ref) = @$r;\n+\t$existing{$gd}{$ref} = $r;\n+}\n+\n+my @new_branch_commits;\n+my $loaded_new_branch_commits = 0;\n+\n+while (<STDIN>) {\n+\tchop;\n+\tmy ($old, $new, $ref) = split / /, $_, 3;\n+\n+\tnext if $old eq $new;\n+\tnext if $new =~ /^0{40}$/;\n+\tnext if skip $ref;\n+\n+\tmy $r = $existing{$git_dir}{$ref};\n+\tif ($r) {\n+\t\t$r->[3] = $new;\n+\t} else {\n+\t\tif ($old =~ /^0{40}$/) {\n+\t\t\tif (!$loaded_new_branch_commits && @new_branch_base) {\n+\t\t\t\topen M,'-|','git','show-ref',@new_branch_base;\n+\t\t\t\twhile (<M>) {\n+\t\t\t\t\t($_) = split / /, $_;\n+\t\t\t\t\tpush @new_branch_commits, $_;\n+\t\t\t\t}\n+\t\t\t\tclose M;\n+\t\t\t\t$loaded_new_branch_commits = 1;\n+\t\t\t}\n+\t\t\t$old = [@new_branch_commits];\n+\t\t} else {\n+\t\t\t$old = [$old];\n+\t\t}\n+\n+\t\t$r = [$git_dir, $ref, $old, $new];\n+\t\t$existing{$git_dir}{$ref} = $r;\n+\t\tpush @$queue, $r;\n+\t}\n+}\n+nstore $queue, $queue_name;\n+\n+flock LOCK, LOCK_UN;\n+close LOCK;\n-- \n1.5.1.rc1.595.gd1206\n"},{"id":"37652","messageId":"17920.38942.364466.642979@lisa.zopyra.com","threadId":"7325","inReplyTo":"20070320043341.GA29436@spearce.org","subject":"Re: [PATCH] contrib/continuous: a continuous integration build manager","fromName":"Bill Lear","fromEmail":"rael@zopyra.com","sentAt":"2007-03-21T02:27:42Z","receivedAt":"2007-03-21T02:27:42Z","isPatch":true,"sender":{"key":"rael@zopyra.com","avatar":"https://gravatar.com/avatar/c4f2d2790ca3828d3b4e7dfebabf61d2fe94fd82fa49cdac2a5295dd2d46a874?d=mp&s=160"},"body":"On Tuesday, March 20, 2007 at 00:33:41 (-0400) Shawn O. Pearce writes:\n>This is a simple but powerful continuous integration build system\n>for Git.  ...\n\nThis looks quite useful.  Thanks for sharing your \"hack\".\n\nFor some reason, this got me to thinking about automated bisection.\n\nThe way I understand bisection to work is that you mark starting\nand ending points (good vs. bad), then you do some work, and\ntell git if the current point is good or bad, continuing on until\nyou find the first baddie.\n\nThis is a very thoughtful piece of software.\n\nHowever, would it be possible to automate this, say by designating a\nscript to run that would automatically return the \"good\" vs. \"bad\",\nallowing you to run off to lunch (or more) and let it determine the\nfirst bad point?\n\nSomething like this:\n\n% git bisect start\n% git bisect bad\n% git bisect good v2.6.13-rc2\n% git bisect run my_script\n\nI suppose one could just write a wrapper script, but perhaps this\nwould be convenient enough...\n\n\nBill\n"}]}