Index: solr/build.xml
===================================================================
--- solr/build.xml	(revision 1310644)
+++ solr/build.xml	(working copy)
@@ -330,8 +330,8 @@
     </ant>
   </target>
 
-	<target name="prepare-release"
-          depends="clean, package, generate-maven-artifacts, sign-artifacts"/>
+  <target name="prepare-release-no-sign" depends="clean, package, generate-maven-artifacts"/>
+  <target name="prepare-release" depends="prepare-release-no-sign, sign-artifacts"/>
  
   <!-- make a distribution -->
   <target name="package" depends="package-src-tgz,create-package"/>
Index: lucene/build.xml
===================================================================
--- lucene/build.xml	(revision 1310644)
+++ lucene/build.xml	(working copy)
@@ -452,7 +452,8 @@
     <copy-to-stage-macro artifacts.dir="${dist.dir}"/>
   </target>
 
-  <target name="prepare-release" depends="clean, dist-all, generate-maven-artifacts, sign-artifacts"/>
+  <target name="prepare-release-no-sign" depends="clean, dist-all, generate-maven-artifacts"/>
+  <target name="prepare-release" depends="prepare-release-no-sign, sign-artifacts"/>
   <target name="stage" depends="prepare-release, copy-to-stage">
 
   </target>
Index: dev-tools/scripts/smokeTestRelease.py
===================================================================
--- dev-tools/scripts/smokeTestRelease.py	(revision 1310644)
+++ dev-tools/scripts/smokeTestRelease.py	(working copy)
@@ -36,6 +36,16 @@
 # must have a working gpg, tar, unzip in your path.  This has been
 # tested on Linux and on Cygwin under Windows 7.
 
+def unshortenURL(url):
+  parsed = urlparse.urlparse(url)
+  if parsed[0] in ('http', 'https'):
+    h = httplib.HTTPConnection(parsed.netloc)
+    h.request('HEAD', parsed.path)
+    response = h.getresponse()
+    if response.status/100 == 3 and response.getheader('Location'):
+      return response.getheader('Location')
+  return url  
+
 def javaExe(version):
   if version == '1.5':
     path = JAVA5_HOME
@@ -345,10 +355,24 @@
     raise RuntimeError('SHA1 digest mismatch for %s: expected %s but got %s' % (artifact, sha1Expected, sha1Actual))
 
 def getDirEntries(urlString):
-  links = getHREFs(urlString)
-  for i, (text, subURL) in enumerate(links):
-    if text == 'Parent Directory' or text == '..':
-      return links[(i+1):]
+  if urlString.startswith('file://'):
+    path = urlString[7:]
+    if path.endswith('/'):
+      path = path[:-1]
+    l = []
+    for ent in os.listdir(path):
+      entPath = '%s/%s' % (path, ent)
+      if os.path.isdir(entPath):
+        entPath += '/'
+        ent += '/'
+      l.append((ent, 'file://%s' % entPath))
+    l.sort()
+    return l
+  else:
+    links = getHREFs(urlString)
+    for i, (text, subURL) in enumerate(links):
+      if text == 'Parent Directory' or text == '..':
+        return links[(i+1):]
 
 def unpack(project, tmpDir, artifact, version):
   destDir = '%s/unpack' % tmpDir
@@ -979,7 +1003,7 @@
   if POMtemplates['solr'] is None:
     raise RuntimeError('No Solr POMs found at %s' % sourceLocation)
   POMtemplates['grandfather'] = [p for p in allPOMtemplates if '/maven/pom.xml.template' in p]
-  if POMtemplates['grandfather'] is None:
+  if len(POMtemplates['grandfather']) == 0:
     raise RuntimeError('No Lucene/Solr grandfather POM found at %s' % sourceLocation)
 
 def crawl(downloadedFiles, urlString, targetDir, exclusions=set()):
@@ -1008,6 +1032,10 @@
   version = sys.argv[2]
   tmpDir = os.path.abspath(sys.argv[3])
 
+  smokeTest(baseURL, version, tmpDir)
+
+def smokeTest(baseURL, version, tmpDir):
+
   if not DEBUG:
     if os.path.exists(tmpDir):
       raise RuntimeError('temp dir %s exists; please remove first' % tmpDir)
@@ -1017,7 +1045,13 @@
   
   lucenePath = None
   solrPath = None
-  print 'Load release URL...'
+  print
+  print 'Load release URL "%s"...' % baseURL
+  newBaseURL = unshortenURL(baseURL)
+  if newBaseURL != baseURL:
+    print '  unshortened: %s' % newBaseURL
+    baseURL = newBaseURL
+    
   for text, subURL in getDirEntries(baseURL):
     if text.lower().find('lucene') != -1:
       lucenePath = subURL
Index: dev-tools/scripts/buildAndPushRelease.py
===================================================================
--- dev-tools/scripts/buildAndPushRelease.py	(revision 1310644)
+++ dev-tools/scripts/buildAndPushRelease.py	(working copy)
@@ -19,18 +19,16 @@
 import os
 import sys
 
-# Usage: python -u buildRelease.py /path/to/checkout version(eg: 3.4.0) gpgKey(eg: 6E68DA61) rcNum user [-prepare] [-push]
+# Usage: python -u buildRelease.py [-sign gpgKey(eg: 6E68DA61)] [-prepare] [-push userName] [-pushLocal dirName] [-smoke tmpDir] /path/to/checkout version(eg: 3.4.0) rcNum(eg: 0)
 #
-# EG: python -u buildRelease.py -prepare -push /lucene/34x 3.4.0 6E68DA61 1 mikemccand
-# 
+# EG: python -u buildRelease.py -prepare -push -sign 6E68DA61 mikemccand /lucene/34x 3.4.0 0
 
-# TODO: also run smokeTestRelease.py?
+# NOTE: if you specify -sign, you have to type in your gpg password at
+# some point while this runs; it's VERY confusing because the output
+# is directed to /tmp/release.log, so, you have to tail that and when
+# GPG wants your password, type it!  Also sometimes you have to type
+# it twice in a row!
 
-# NOTE: you have to type in your gpg password at some point while this
-# runs; it's VERY confusing because the output is directed to
-# /tmp/release.log, so, you have to tail that and when GPG wants your
-# password, type it!
-
 LOG = '/tmp/release.log'
 
 def log(msg):
@@ -93,10 +91,21 @@
   
   print '  lucene prepare-release'
   os.chdir('lucene')
-  run('ant -Dversion=%s -Dspecversion=%s -Dgpg.key=%s prepare-release' % (version, version, gpgKeyID))
+  cmd = 'ant -Dversion=%s -Dspecversion=%s' % (version, version)
+  if gpgKeyID is not None:
+    cmd += ' -Dgpg.key=%s prepare-release' % gpgKeyID
+  else:
+    cmd += ' prepare-release-no-sign'
+  run(cmd)
+  
   print '  solr prepare-release'
   os.chdir('../solr')
-  run('ant -Dversion=%s -Dspecversion=%s -Dgpg.key=%s prepare-release' % (version, version, gpgKeyID))
+  cmd = 'ant -Dversion=%s -Dspecversion=%s' % (version, version)
+  if gpgKeyID is not None:
+    cmd += ' -Dgpg.key=%s prepare-release' % gpgKeyID
+  else:
+    cmd += ' prepare-release-no-sign'
+  run(cmd)
   print '  done!'
   print
   return rev
@@ -149,21 +158,113 @@
   print '  chmod...'
   run('ssh %s@people.apache.org "chmod -R a+rX-w public_html/staging_area/%s"' % (username, dir))
 
-  print '  done!  URL: https://people.apache.org/~%s/staging_area/%s' % (username, dir)
+  print '  done!'
+  url = 'https://people.apache.org/~%s/staging_area/%s' % (username, dir)
+  return url
 
+def pushLocal(version, root, rev, rcNum, localDir):
+  print 'Push local [%s]...' % localDir
+  os.makedirs(localDir)
+
+  dir = 'lucene-solr-%s-RC%d-rev%s' % (version, rcNum, rev)
+  os.makedirs('%s/%s/lucene' % (localDir, dir))
+  os.makedirs('%s/%s/solr' % (localDir, dir))
+  print '  Lucene'
+  os.chdir('%s/lucene/dist' % root)
+  print '    zip...'
+  if os.path.exists('lucene.tar.bz2'):
+    os.remove('lucene.tar.bz2')
+  run('tar cjf lucene.tar.bz2 *')
+
+  os.chdir('%s/%s/lucene' % (localDir, dir))
+  print '    unzip...'
+  run('tar xjf "%s/lucene/dist/lucene.tar.bz2"' % root)
+  os.remove('%s/lucene/dist/lucene.tar.bz2' % root)
+  print '    copy changes...'
+  run('cp -r "%s/lucene/build/docs/changes" changes-%s' % (root, version))
+
+  print '  Solr'
+  os.chdir('%s/solr/package' % root)
+  print '    zip...'
+  if os.path.exists('solr.tar.bz2'):
+    os.remove('solr.tar.bz2')
+  run('tar cjf solr.tar.bz2 *')
+  print '    unzip...'
+  os.chdir('%s/%s/solr' % (localDir, dir))
+  run('tar xjf "%s/solr/package/solr.tar.bz2"' % root)
+  os.remove('%s/solr/package/solr.tar.bz2' % root)
+
+  print '  KEYS'
+  run('wget http://people.apache.org/keys/group/lucene.asc')
+  os.rename('lucene.asc', 'KEYS')
+  run('chmod a+r-w KEYS')
+  run('cp KEYS ../lucene')
+
+  print '  chmod...'
+  os.chdir('..')
+  run('chmod -R a+rX-w .')
+
+  print '  done!'
+  return 'file://%s/%s' % (os.path.abspath(localDir), dir)
   
 def main():
   doPrepare = '-prepare' in sys.argv
   if doPrepare:
     sys.argv.remove('-prepare')
-  doPush = '-push' in sys.argv
-  if doPush:
-    sys.argv.remove('-push')
+
+  try:
+    idx = sys.argv.index('-push')
+  except ValueError:
+    pushRemote = False
+  else:
+    pushRemote = True
+    username = sys.argv[idx+1]
+    del sys.argv[idx:idx+2]
+
+  try:
+    idx = sys.argv.index('-smoke')
+  except ValueError:
+    smokeTmpDir = None
+  else:
+    smokeTmpDir = sys.argv[idx+1]
+    del sys.argv[idx:idx+2]
+    if os.path.exists(smokeTmpDir):
+      print
+      print 'ERROR: smoke tmpDir "%s" exists; please remove first' % smokeTmpDir
+      print
+      sys.exit(1)
+    
+  try:
+    idx = sys.argv.index('-pushLocal')
+  except ValueError:
+    pushLocal = False
+  else:
+    pushLocal = True
+    localStagingDir = sys.argv[idx+1]
+    del sys.argv[idx:idx+2]
+    if os.path.exists(localStagingDir):
+      print
+      print 'ERROR: pushLocal dir "%s" exists; please remove first' % localStagingDir
+      print
+      sys.exit(1)
+
+  if pushRemote and pushLocal:
+    print
+    print 'ERROR: specify at most one of -push or -pushLocal (got both)'
+    print
+    sys.exit(1)
+
+  try:
+    idx = sys.argv.index('-sign')
+  except ValueError:
+    gpgKeyID = None
+  else:
+    gpgKeyID = sys.argv[idx+1]
+    del sys.argv[idx:idx+2]
+    
   root = os.path.abspath(sys.argv[1])
   version = sys.argv[2]
-  gpgKeyID = sys.argv[3]
-  rcNum = int(sys.argv[4])
-  username = sys.argv[5]
+  rcNum = int(sys.argv[3])
 
   if doPrepare:
     rev = prepare(root, version, gpgKeyID)
@@ -171,8 +272,20 @@
     os.chdir(root)
     rev = open('rev.txt').read()
 
-  if doPush:
-    push(version, root, rev, rcNum, username)
+  if pushRemote:
+    url = push(version, root, rev, rcNum, username)
+  elif pushLocal:
+    url = pushLocal(version, root, rev, rcNum, localStaging)
+  else:
+    url = NOne
+
+  if url is not None:
+    print '  URL: %s' % url
+
+  if smokeTmpDir is not None:
+    import smokeTestRelease
+    smokeTestRelease.DEBUG = False
+    smokeTestRelease.smokeTest(url, version, smokeTmpDir)
     
 if __name__ == '__main__':
   main()
