<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -12,6 +12,9 @@ import fabric.version
 _version = fabric.version.get_version
 
 
+docs_host = 'jforcier@fabfile.org'
+
+
 def test(args=&quot;&quot;):
     &quot;&quot;&quot;
     Run all unit tests and doctests.
@@ -34,7 +37,7 @@ def build_docs(clean='no', browse='no'):
     local('cd docs; make %shtml%s' % (c, b), capture=False)
 
 
-@hosts('jforcier@fabfile.org')
+@hosts(docs_host)
 def push_docs():
     &quot;&quot;&quot;
     Build and push the Sphinx docs to docs.fabfile.org
@@ -47,19 +50,23 @@ def push_docs():
 def _code_version_is_tagged():
     return local('git tag | egrep &quot;^%s$&quot;' % _version('short'))
 
-def _update_code_version():
+def _update_code_version(force):
     &quot;&quot;&quot;
     Update version data structure in-code and commit that change to git.
+
+    Normally, if the version file has not been modified, we abort assuming the
+    user quit without saving. Specify ``force=yes`` to override this.
     &quot;&quot;&quot;
+    version_file = &quot;fabric/version.py&quot;
     raw_input(&quot;Work has been done since last tag, version update is needed. Hit Enter to load version info in your editor: &quot;)
-    local(&quot;$EDITOR fabric/version.py&quot;, capture=False)
+    local(&quot;$EDITOR %s&quot; % version_file, capture=False)
     # Try to detect whether user bailed out of the edit
-    if not local('git diff -- fabric/version.py'):
+    if not local(&quot;git diff -- %s&quot; % version_file) and not force:
         abort(&quot;You seem to have aborted the file edit, so I'm aborting too.&quot;)
     # Reload version module to get new version
     reload(fabric.version)
     # Commit the version update
-    local(&quot;git add fabric/version.py&quot;, capture=False)
+    local(&quot;git add %s&quot; % version_file, capture=False)
     local(&quot;git commit -m \&quot;Cut %s\&quot;&quot; % _version('verbose'), capture=False)
 
 def _commits_since_tag():
@@ -68,34 +75,67 @@ def _commits_since_tag():
     &quot;&quot;&quot;
     return local(&quot;git log %s..&quot; % _version('short'))
 
-def tag():
+def tag(force='no', push='no'):
     &quot;&quot;&quot;
-    Tag a new release of the software
+    Tag a new release.
+
+    Normally, if a Git tag exists matching the current version, and no Git
+    commits appear after that tag, we abort assuming the user is making a
+    mistake or forgot to commit their work.
+
+    To override this -- i.e. to re-tag and re-upload -- specify ``force=yes``.
+    We assume you know what you're doing if you use this.
+
+    By default we do not push the tag remotely; specify ``push=yes`` to force a
+    ``git push origin &lt;tag&gt;``.
     &quot;&quot;&quot;
+    force = force.lower() in ['y', 'yes']
     with settings(warn_only=True):
         # Does the current in-code version exist as a Git tag already?
         # If so, this means we haven't updated the in-code version specifier
         # yet, and need to do so.
         if _code_version_is_tagged():
             # That is, if any work has been done since. Sanity check!
-            if not _commits_since_tag():
+            if not _commits_since_tag() and not force:
                 abort(&quot;No work done since last tag!&quot;)
             # Open editor, update version, commit that change to Git.
-            _update_code_version()
+            _update_code_version(force)
         # If the tag doesn't exist, the user has already updated version info
         # and we can just move on.
         else:
             print(&quot;Version has already been updated, no need to edit...&quot;)
         # At this point, we've incremented the in-code version and just need to
         # tag it in Git.
-        local(&quot;git tag -am \&quot;Fabric %s\&quot; %s&quot; % (
+        f = 'f' if force else ''
+        local(&quot;git tag -%sam \&quot;Fabric %s\&quot; %s&quot; % (
+            f,
             _version('verbose'),
             _version('short')
         ), capture=False)
+        # And push to the central server, if we were told to
+        if push.lower() in ['y', 'yes']:
+            local(&quot;git push origin %s&quot; % _version('short'), capture=False)
 
 
 def build():
     &quot;&quot;&quot;
-    Create distribution package via setup.py
+    Build (but don't upload) via setup.py
     &quot;&quot;&quot;
     local('python setup.py sdist', capture=False)
+
+
+def upload():
+    &quot;&quot;&quot;
+    Build, register and upload to PyPI
+    &quot;&quot;&quot;
+    local('python setup.py sdist register upload', capture=False)
+
+
+def release(force='no'):
+    &quot;&quot;&quot;
+    Tag/push, build, upload new version and build/upload documentation.
+    &quot;&quot;&quot;
+    tag(force=force, push='yes')
+    upload()
+    with settings(host_string=docs_host):
+        push_docs()</diff>
      <filename>fabfile.py</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>a69dde19c5be0e4d5c83464c0deabe943a128550</id>
    </parent>
  </parents>
  <author>
    <name>Jeff Forcier</name>
    <email>jeff@bitprophet.org</email>
  </author>
  <url>http://github.com/bitprophet/fabric/commit/63082f8dfd2094139903f85be78d08accf50e748</url>
  <id>63082f8dfd2094139903f85be78d08accf50e748</id>
  <committed-date>2009-11-08T16:38:37-08:00</committed-date>
  <authored-date>2009-11-08T16:38:37-08:00</authored-date>
  <message>Add release task and related tasks.</message>
  <tree>7f789660e8f98934f1b37f8aaf47cff984c54f2b</tree>
  <committer>
    <name>Jeff Forcier</name>
    <email>jeff@bitprophet.org</email>
  </committer>
</commit>
