summaryrefslogtreecommitdiffstats
path: root/WebKitTools/Scripts/modules/commands/upload.py
diff options
context:
space:
mode:
Diffstat (limited to 'WebKitTools/Scripts/modules/commands/upload.py')
-rw-r--r--WebKitTools/Scripts/modules/commands/upload.py246
1 files changed, 246 insertions, 0 deletions
diff --git a/WebKitTools/Scripts/modules/commands/upload.py b/WebKitTools/Scripts/modules/commands/upload.py
new file mode 100644
index 0000000..1f892a1
--- /dev/null
+++ b/WebKitTools/Scripts/modules/commands/upload.py
@@ -0,0 +1,246 @@
+#!/usr/bin/env python
+# Copyright (c) 2009, Google Inc. All rights reserved.
+# Copyright (c) 2009 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import os
+import StringIO
+import sys
+
+from optparse import make_option
+
+from modules.bugzilla import parse_bug_id
+from modules.grammar import pluralize
+from modules.logging import error, log
+from modules.multicommandtool import Command
+
+# FIXME: Requires unit test.
+class CommitMessageForCurrentDiff(Command):
+ name = "commit-message"
+ show_in_main_help = False
+ def __init__(self):
+ Command.__init__(self, "Print a commit message suitable for the uncommitted changes")
+
+ def execute(self, options, args, tool):
+ os.chdir(tool.scm().checkout_root)
+ print "%s" % tool.scm().commit_message_for_this_commit().message()
+
+
+class ObsoleteAttachments(Command):
+ name = "obsolete-attachments"
+ show_in_main_help = False
+ def __init__(self):
+ Command.__init__(self, "Mark all attachments on a bug as obsolete", "BUGID")
+
+ def execute(self, options, args, tool):
+ bug_id = args[0]
+ attachments = tool.bugs.fetch_attachments_from_bug(bug_id)
+ for attachment in attachments:
+ if not attachment["is_obsolete"]:
+ tool.bugs.obsolete_attachment(attachment["id"])
+
+
+class PostDiff(Command):
+ name = "post-diff"
+ show_in_main_help = True
+ def __init__(self):
+ options = [
+ make_option("-m", "--description", action="store", type="string", dest="description", help="Description string for the attachment (default: \"patch\")"),
+ ]
+ options += self.posting_options()
+ Command.__init__(self, "Attach the current working directory diff to a bug as a patch file", "[BUGID]", options=options)
+
+ @staticmethod
+ def posting_options():
+ return [
+ make_option("--no-obsolete", action="store_false", dest="obsolete_patches", default=True, help="Do not obsolete old patches before posting this one."),
+ make_option("--no-review", action="store_false", dest="review", default=True, help="Do not mark the patch for review."),
+ make_option("--request-commit", action="store_true", dest="request_commit", default=False, help="Mark the patch as needing auto-commit after review."),
+ ]
+
+ @staticmethod
+ def obsolete_patches_on_bug(bug_id, bugs):
+ patches = bugs.fetch_patches_from_bug(bug_id)
+ if len(patches):
+ log("Obsoleting %s on bug %s" % (pluralize("old patch", len(patches)), bug_id))
+ for patch in patches:
+ bugs.obsolete_attachment(patch["id"])
+
+ def execute(self, options, args, tool):
+ # Perfer a bug id passed as an argument over a bug url in the diff (i.e. ChangeLogs).
+ bug_id = (args and args[0]) or parse_bug_id(tool.scm().create_patch())
+ if not bug_id:
+ error("No bug id passed and no bug url found in diff, can't post.")
+
+ if options.obsolete_patches:
+ self.obsolete_patches_on_bug(bug_id, tool.bugs)
+
+ diff = tool.scm().create_patch()
+ diff_file = StringIO.StringIO(diff) # add_patch_to_bug expects a file-like object
+
+ description = options.description or "Patch"
+ tool.bugs.add_patch_to_bug(bug_id, diff_file, description, mark_for_review=options.review, mark_for_commit_queue=options.request_commit)
+
+
+class PostCommits(Command):
+ name = "post-commits"
+ show_in_main_help = True
+ def __init__(self):
+ options = [
+ make_option("-b", "--bug-id", action="store", type="string", dest="bug_id", help="Specify bug id if no URL is provided in the commit log."),
+ make_option("--add-log-as-comment", action="store_true", dest="add_log_as_comment", default=False, help="Add commit log message as a comment when uploading the patch."),
+ make_option("-m", "--description", action="store", type="string", dest="description", help="Description string for the attachment (default: description from commit message)"),
+ ]
+ options += PostDiff.posting_options()
+ Command.__init__(self, "Attach a range of local commits to bugs as patch files", "COMMITISH", options=options, requires_local_commits=True)
+
+ def _comment_text_for_commit(self, options, commit_message, tool, commit_id):
+ comment_text = None
+ if (options.add_log_as_comment):
+ comment_text = commit_message.body(lstrip=True)
+ comment_text += "---\n"
+ comment_text += tool.scm().files_changed_summary_for_commit(commit_id)
+ return comment_text
+
+ def _diff_file_for_commit(self, tool, commit_id):
+ diff = tool.scm().create_patch_from_local_commit(commit_id)
+ return StringIO.StringIO(diff) # add_patch_to_bug expects a file-like object
+
+ def execute(self, options, args, tool):
+ commit_ids = tool.scm().commit_ids_from_commitish_arguments(args)
+ if len(commit_ids) > 10: # We could lower this limit, 10 is too many for one bug as-is.
+ error("bugzilla-tool does not support attaching %s at once. Are you sure you passed the right commit range?" % (pluralize("patch", len(commit_ids))))
+
+ have_obsoleted_patches = set()
+ for commit_id in commit_ids:
+ commit_message = tool.scm().commit_message_for_local_commit(commit_id)
+
+ # Prefer --bug-id=, then a bug url in the commit message, then a bug url in the entire commit diff (i.e. ChangeLogs).
+ bug_id = options.bug_id or parse_bug_id(commit_message.message()) or parse_bug_id(tool.scm().create_patch_from_local_commit(commit_id))
+ if not bug_id:
+ log("Skipping %s: No bug id found in commit or specified with --bug-id." % commit_id)
+ continue
+
+ if options.obsolete_patches and bug_id not in have_obsoleted_patches:
+ PostDiff.obsolete_patches_on_bug(bug_id, tool.bugs)
+ have_obsoleted_patches.add(bug_id)
+
+ diff_file = self._diff_file_for_commit(tool, commit_id)
+ description = options.description or commit_message.description(lstrip=True, strip_url=True)
+ comment_text = self._comment_text_for_commit(options, commit_message, tool, commit_id)
+ tool.bugs.add_patch_to_bug(bug_id, diff_file, description, comment_text, mark_for_review=options.review, mark_for_commit_queue=options.request_commit)
+
+
+class MarkFixed(Command):
+ name = "mark-fixed"
+ show_in_main_help = False
+ def __init__(self):
+ Command.__init__(self, "Mark the specified bug as fixed", "BUG_ID REASON")
+
+ def execute(self, options, args, tool):
+ tool.bugs.close_bug_as_fixed(args[0], args[1])
+
+
+# FIXME: Requires unit test. Blocking issue: too complex for now.
+class CreateBug(Command):
+ name = "create-bug"
+ show_in_main_help = True
+ def __init__(self):
+ options = [
+ make_option("--cc", action="store", type="string", dest="cc", help="Comma-separated list of email addresses to carbon-copy."),
+ make_option("--component", action="store", type="string", dest="component", help="Component for the new bug."),
+ make_option("--no-prompt", action="store_false", dest="prompt", default=True, help="Do not prompt for bug title and comment; use commit log instead."),
+ make_option("--no-review", action="store_false", dest="review", default=True, help="Do not mark the patch for review."),
+ make_option("--request-commit", action="store_true", dest="request_commit", default=False, help="Mark the patch as needing auto-commit after review."),
+ ]
+ Command.__init__(self, "Create a bug from local changes or local commits", "[COMMITISH]", options=options)
+
+ def create_bug_from_commit(self, options, args, tool):
+ commit_ids = tool.scm().commit_ids_from_commitish_arguments(args)
+ if len(commit_ids) > 3:
+ error("Are you sure you want to create one bug with %s patches?" % len(commit_ids))
+
+ commit_id = commit_ids[0]
+
+ bug_title = ""
+ comment_text = ""
+ if options.prompt:
+ (bug_title, comment_text) = self.prompt_for_bug_title_and_comment()
+ else:
+ commit_message = tool.scm().commit_message_for_local_commit(commit_id)
+ bug_title = commit_message.description(lstrip=True, strip_url=True)
+ comment_text = commit_message.body(lstrip=True)
+ comment_text += "---\n"
+ comment_text += tool.scm().files_changed_summary_for_commit(commit_id)
+
+ diff = tool.scm().create_patch_from_local_commit(commit_id)
+ diff_file = StringIO.StringIO(diff) # create_bug_with_patch expects a file-like object
+ bug_id = tool.bugs.create_bug_with_patch(bug_title, comment_text, options.component, diff_file, "Patch", cc=options.cc, mark_for_review=options.review, mark_for_commit_queue=options.request_commit)
+
+ if bug_id and len(commit_ids) > 1:
+ options.bug_id = bug_id
+ options.obsolete_patches = False
+ # FIXME: We should pass through --no-comment switch as well.
+ PostCommits.execute(self, options, commit_ids[1:], tool)
+
+ def create_bug_from_patch(self, options, args, tool):
+ bug_title = ""
+ comment_text = ""
+ if options.prompt:
+ (bug_title, comment_text) = self.prompt_for_bug_title_and_comment()
+ else:
+ commit_message = tool.scm().commit_message_for_this_commit()
+ bug_title = commit_message.description(lstrip=True, strip_url=True)
+ comment_text = commit_message.body(lstrip=True)
+
+ diff = tool.scm().create_patch()
+ diff_file = StringIO.StringIO(diff) # create_bug_with_patch expects a file-like object
+ bug_id = tool.bugs.create_bug_with_patch(bug_title, comment_text, options.component, diff_file, "Patch", cc=options.cc, mark_for_review=options.review, mark_for_commit_queue=options.request_commit)
+
+ def prompt_for_bug_title_and_comment(self):
+ bug_title = raw_input("Bug title: ")
+ print "Bug comment (hit ^D on blank line to end):"
+ lines = sys.stdin.readlines()
+ try:
+ sys.stdin.seek(0, os.SEEK_END)
+ except IOError:
+ # Cygwin raises an Illegal Seek (errno 29) exception when the above
+ # seek() call is made. Ignoring it seems to cause no harm.
+ # FIXME: Figure out a way to get avoid the exception in the first
+ # place.
+ pass
+ comment_text = "".join(lines)
+ return (bug_title, comment_text)
+
+ def execute(self, options, args, tool):
+ if len(args):
+ if (not tool.scm().supports_local_commits()):
+ error("Extra arguments not supported; patch is taken from working directory.")
+ self.create_bug_from_commit(options, args, tool)
+ else:
+ self.create_bug_from_patch(options, args, tool)