diff options
-rw-r--r-- | COPYING | 340 | ||||
-rw-r--r-- | TODO | 39 | ||||
-rwxr-xr-x | git-bz | 755 |
3 files changed, 1134 insertions, 0 deletions
@@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. @@ -0,0 +1,39 @@ +Ideas about additions for git-bz +================================ + +The presence of an idea here does not necessarily imply that I have +any intention of working on it myself. + +- Owen + + +-u/--add-url Option + + When specified, local commits are edited to append the Bug URL. + + attach: Before attaching them to the bug + + Requires: clean index and a series of bugs that leads to the + current HEAD commit. + Can't be the default because commits might have already been pushed + Should be smart if the URL is already in the bug + Might be useful to have a standalone 'add-url' subcommand that can + be used to fix up if you forget to specify it. + +Use XML-RPC when available. + + Maybe use python-bugzilla: http://fedorahosted.org/python-bugzilla/ + + Not sure there are a lot of advantages to this; one thing that it + might be possible to do with this is allow the user to specify only + the product and get an interactive list of components. Also, better + error handling. + +Handle redirects: + + Should follow redirects, both to different URLs and http => https + +Better display of errors + + Currently specifying a non-existent product/component just dumps + out raw HTML for the reply. Etc. @@ -0,0 +1,755 @@ +#!/usr/bin/python +# +# git-bz - git subcommand to integrate with bugzilla +# +# Copyright (C) 2008 Owen Taylor +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, If not, see +# http://www.gnu.org/licenses/. +# +# Patches for git-bz +# ================== +# Send to Owen Taylor <otaylor@fishsoup.net> +# +# Installation +# ============ +# Copy or symlink somewhere in your path. You'll need to have GitPython installed. +# See: http://gitorious.org/projects/git-python/ +# +# Usage +# ===== +# +# git bz apply [options] <bug reference> +# +# For each patch attachment (except for obsolete patches) of the specified +# bug, prompts whether to apply. If prompt is agreed to runs 'git am' on +# the patch to apply it to the current branch. Aborts if 'git am' fails to +# allow cleaning up conflicts. +# +# Example: +# +# git bz apply bugzillla.gnome.org:1234 +# +# git bz attach [-<N>] [options] <bug reference> [<since | <revision range>] +# +# For each commit or commits, formats as a patch and attaches to the +# specified bug, with the subject of the commit as the description and +# the body of the commit as the comment. The patch formatting and and +# specification of which commits are as for 'git format-patch' +# +# Prompts before actually doing anything to avoid mistakes. +# +# Examples: +# +# # Attach the last commit +# git bz attach bugzilla.gnome.org:1234 HEAD^ +# +# # Attach everything starting at an old commit +# git bz attach bugzilla.gnome.org:1234 b50ea9bd^ +# +# # Attach a single old commit +# git bz attach bugzilla.gnome.org:1234 b50ea9bd -1 +# +# git bz file [-<N>] [options] <product>/<component> [<since> | <revision range>] +# +# Like 'attach', but files a new bug. Opens an editor for the user to +# enter the summary and description for the bug. If only a single commit +# is named summary defaults to the subject of the commit, and the description +# to the body of the bug +# +# Examples: +# +# # File the last commit as a new bug on the default tracker +# git bz file HEAD^ +# +# # File a bug with a series of patches starting from an old commit +# # on a different bug tracker +# git bz -b bugs.freedesktop.org file b50ea9bd^ -1 +# +# Authentication +# ============== +# In order to use git-bz you need to already be logged into the bug tracker +# in your web browser, and git-bz reads your browser cookie. Currently only +# Firefox 3 is supported, and only on Linux. Patches to add more support and +# to allow configuring username/password directly per bug tracker accepted. +# +# Bug references +# ============== +# Ways to refer to a bug: +# <id> : bug # on the default bug tracker +# <host>:<id> : bug # on the given host +# <alias>:<id> : bug # on the given bug tracker alias (see below) +# <url> : An URL of the form http://<hostname>/show_bug.cgi?id=<id> +# +# Aliases +# ======= +# You can create short aliases for different bug trackers as follows +# +# git config --global bz-tracker.bgo.host bugzilla.gnome.org +# +# And you can set the default bug tracker with: +# +# git config --global bz.default-tracker bgo +# +# Per Tracker Configuration +# ========================= +# git-bz needs some configuration specific to the bugzilla instance (tracker), +# in particular it needs to know initial field values to use when submitting +# bugs; legal values for some fields depend on the instance. +# +# You can also set whether to use http or https by setting the 'https' variabe +# For https, *certificates are not checked* so you are completely vulnerable +# to DNS spoofing and man-in-the-middle attacks. Blame httplib. +# +# Configuration comes from 4 sources, in descending order of priority +# +# 1) git configuration variables specified for the alias. +# +# git config --global bz-tracker.bgo.default-bug-severity trivial +# +# 2) git configuration variables specified for the host +# +# git config --global bz-tracker.bugzilla.gnome.org.default-bug-severity trivial +# +# 3) Host specific configuration in this file, see the CONFIG variable below +# +# 4) Default configuration in this file, see the DEFAULT_CONFIG variable below +# +# In general, settings that are necessary to make a popular bugzilla instance +# work should be submitted back to me and go in the CONFIG variable. +# +DEFAULT_CONFIG = \ +""" +default-assigned-to = +default-bug-file-loc = +default-bug-severity = +default-op-sys = All +default-priority = P5 +default-rep-platform = All +default-version = unspecified +""" + +CONFIG = {} + +CONFIG['bugs.freedesktop.org'] = \ +""" +https = true +default-priority = medium +""" + +CONFIG['bugzilla.gnome.org'] = \ +""" +default-priority = Normal +""" + +CONFIG['bugzilla.mozilla.org'] = \ +""" +https = true +default-priority = --- +""" + +################################################################################ + +from ConfigParser import RawConfigParser +import git +from httplib import HTTPConnection, HTTPSConnection +from optparse import OptionParser +import os +from pysqlite2 import dbapi2 as sqlite +import re +from StringIO import StringIO +import subprocess +import sys +import tempfile +import time +import urllib +from xml.etree.cElementTree import ElementTree + +# Globals +# ======= + +# git.Repo() instance +global_repo = None + +# options dictionary from optparse +global_options = None + +# Utility functions for git +# ========================= + +def get_commits(since_or_revision_range): + if global_options.num: + commits = git.Commit.find_all(repo, since_or_revision_range, max_count=global_options.num) + else: + # git format-patch has special handling of specifying a single revision that is + # different than git-rev-list. Match that. + try: + # See if the argument identifies a single revision + rev = global_repo.git.rev_parse(since_or_revision_range, verify=True) + revision_range = rev + ".." + except git.errors.GitCommandError: + # If not, assume the argument is a range + revision_range = since_or_revision_range + + commits = git.Commit.find_all(repo, revision_range) + + if len(commits) == 0: + die("'%s' does not name any commits. Use HEAD^ to specify just the last commit" % + since_or_revision_range) + + return commits + +def get_patch(commit): + return global_repo.git.format_patch(commit.id + "^.." + commit.id, stdout=True) + +def get_body(commit): + return global_repo.git.log(commit.id + "^.." + commit.id, pretty="format:%b") + +# Per-tracker configuration variables +# =================================== + +def get_default_tracker(): + try: + return global_repo.git.config('bz.default-tracker', get=True) + except git.errors.GitCommandError: + return 'bugzilla.gnome.org' + +def resolve_host_alias(alias): + try: + return global_repo.git.config('bz-tracker.' + alias + '.host', get=True) + except git.errors.GitCommandError: + return alias + +def split_local_config(config_text): + result = {} + + for line in config_text.split("\n"): + line = re.sub("#.*", "", line) + line = line.strip() + if line == "": + continue + m = re.match("([a-zA-Z0-9-]+)\s*=\s*(.*)", line) + if not m: + die("Bad config line '%s'" % line) + + param = m.group(1) + value = m.group(2) + + result[param] = value + + return result + +def get_git_config(name): + try: + name = name.replace(".", r"\.") + config_options = global_repo.git.config(r'bz-tracker\.' + name + r'\..*', get_regexp=True) + except git.errors.GitCommandError: + return {} + + result = {} + for line in config_options.split("\n"): + line = line.strip() + m = re.match("(\S+)\s+(.*)", line) + key = m.group(1) + value = m.group(2) + + m = re.match(r'bz-tracker\.' + name + r'\.(.*)', key) + param = m.group(1) + + result[param] = value + + return result + +# We only ever should be the config for one tracker in the course of a single run +cached_config = None +cached_config_tracker = None + +def get_config(tracker): + global cached_config + + if cached_config == None: + cached_config_tracker = tracker + host = resolve_host_alias(tracker) + cached_config = split_local_config(DEFAULT_CONFIG) + if host in CONFIG: + cached_config.update(split_local_config(CONFIG[host])) + cached_config.update(get_git_config(host)) + if tracker != host: + cached_config.update(get_git_config(tracker)) + + assert cached_config_tracker == tracker + + return cached_config + +def tracker_uses_https(tracker): + config = get_config(tracker) + return 'https' in config and config['https'] == 'true' + +def get_default_fields(tracker): + config = get_config(tracker) + + default_fields = {} + + for key, value in config.iteritems(): + if key.startswith("default-"): + param = key[8:].replace("-", "_") + default_fields[param] = value + + return default_fields + +# Utility functions for bugzilla +# ============================== + +def resolve_bug_reference(bug_reference): + m = re.match("http(s?)://([^/]+)/show_bug.cgi\?id=([^&]+)", bug_reference) + if m: + return m.group(2), m.group(1) != None, m.group(3) + + colon = bug_reference.find(":") + if colon > 0: + tracker = bug_reference[0:colon] + id = bug_reference[colon + 1:] + else: + tracker = get_default_tracker() + id = bug_reference + + host = resolve_host_alias(tracker) + https = tracker_uses_https(tracker) + + if not re.match(r"^.*\.[a-zA-Z]{2,}$", host): + die("'%s' doesn't look like a valid bugzilla host or alias" % host) + + return host, https, id + +def get_bugzilla_cookies(host): + profiles_dir = os.path.expanduser("~/.mozilla/firefox") + + profile_path = None + + cp = RawConfigParser() + cp.read(os.path.join(profiles_dir, "profiles.ini")) + for section in cp.sections(): + if cp.has_option(section, "Default") and cp.get(section, "Default").strip() == "1": + profile_path = os.path.join(profiles_dir, cp.get(section, "Path").strip()) + + if not profile_path: + die("Cannot find default Firefox profile") + + cookies_sqlite = os.path.join(profile_path, "cookies.sqlite") + if not os.path.exists(cookies_sqlite): + die("%s doesn't exist. Only Firefox 3 is supported currently") + + result = {} + + connection = sqlite.connect(cookies_sqlite) + cursor = connection.cursor() + cursor.execute("select name,value,path,expiry from moz_cookies where host = :host", { 'host': host }) + + now = time.time() + for name,value,path,expiry in cursor.fetchall(): + # Excessive caution: toss out values that need to be quoted in a cookie header + if float(expiry) > now and not re.search(r'[()<>@,;:\\"/\[\]?={} \t]', value): + result[name] = value + connection.close() + + if not ('Bugzilla_login' in result and 'Bugzilla_logincookie' in result): + die("You don't appear to be signed into %s; please log in with Firefox" % host) + + return result + +# Based on http://code.activestate.com/recipes/146306/ - Wade Leftwich +def encode_multipart_formdata(fields, files): + """ + fields is a dictionary of { name : value } for regular form fields. + files is a dictionary of { name : ( filename, content_type, value) } for data to be uploaded as files + Return (content_type, body) ready for httplib.HTTPContent instance + """ + BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$' + CRLF = '\r\n' + L = [] + for key in sorted(fields.keys()): + value = fields[key] + L.append('--' + BOUNDARY) + L.append('Content-Disposition: form-data; name="%s"' % key) + L.append('') + L.append(value) + for key in sorted(files.keys()): + (filename, content_type, value) = files[key] + L.append('--' + BOUNDARY) + L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename)) + L.append('Content-Type: %s' % content_type) + L.append('') + L.append(value) + L.append('--' + BOUNDARY + '--') + L.append('') + body = CRLF.join(L) + content_type = 'multipart/form-data; boundary=%s' % BOUNDARY + return content_type, body + +# General Utility Functions +# ========================= + +def make_filename(description): + filename = re.sub(r"\s+", "-", description) + filename = re.sub(r"[^A-Za-z0-9-]+", "", filename) + filename = filename[0:50] + + return filename + +def edit(filename): + editor = None + if 'GIT_EDITOR' in os.environ: + editor = os.environ['GIT_EDITOR'] + if editor == None: + try: + editor = global_repo.git.config('core.editor', get=True) + except git.errors.GitCommandError: + pass + if editor == None and 'EDITOR' in os.environ: + editor = os.environ['EDITOR'] + if editor == None: + editor = "vi" + + process = subprocess.Popen(editor + " " + filename, shell=True) + process.wait() + if process.returncode != 0: + die("Editor exited with non-zero return code") + +def prompt(message): + print message, "[yn] ", + line = sys.stdin.readline().strip() + return line == 'y' or line == 'Y' + +def die(message): + print >>sys.stderr, message + sys.exit(1) + +# Classes for bug handling +# ======================== + +class BugPatch(object): + def __init__(self, attach_id, description, date): + self.attach_id = attach_id + self.description = description + self.date = date + +class Bug(object): + def __init__(self, host, https): + self.host = host + self.https = https + self.id = None + self.product = None + self.component = None + self.short_desc = None + self.patches = [] + + self.cookies = get_bugzilla_cookies(host) + if self.https: + self.connection = HTTPSConnection(self.host, 443) + else: + self.connection = HTTPConnection(self.host, 80) + + def _send_request(self, method, url, data=None, headers={}): + headers = dict(headers) + cookie_string = ("Bugzilla_login=%s; Bugzilla_logincookie=%s" % + (self.cookies['Bugzilla_login'], self.cookies['Bugzilla_logincookie'])) + headers['Cookie'] = cookie_string + headers['User-Agent'] = "git-bz" + + self.connection.request(method, url, data, headers) + + def _send_post(self, url, fields, files): + content_type, body = encode_multipart_formdata(fields, files) + self._send_request("POST", url, data=body, headers={ 'Content-Type': content_type }) + + def _load(self, id): + url = "/show_bug.cgi?id=" + id + "&ctype=xml" + + self._send_request("GET", url) + + response = self.connection.getresponse() + + if response.status != 200: + die ("Failed to retrieve bug information: %d" % response.status) + + etree = ElementTree() + etree.parse(response) + + bug = etree.find("bug") + error = bug.get("error") + if error != None: + die ("Failed to retrieve bug information: %s" % error) + + self.id = int(bug.find("bug_id").text) + self.short_desc = bug.find("short_desc").text + + for attachment in bug.findall("attachment"): + if attachment.get("ispatch") == "1" and not attachment.get("isobsolete") == "1" : + attach_id = int(attachment.find("attachid").text) + description = attachment.find("desc").text + date = attachment.find("date").text + self.patches.append(BugPatch(attach_id, description, date)) + + def _create(self, product, component, short_desc, comment, default_fields): + fields = dict(default_fields) + fields['product'] = product + fields['component'] = component + fields['short_desc'] = short_desc + fields['comment'] = comment + + files = {} + + self._send_post("/post_bug.cgi", fields, files) + + response = self.connection.getresponse() + response_data = response.read() + + if response.status != 200: + print response_data + die("Failed to create bug: %d" % response.status) + + m = re.search(r"<title>\s*Bug\s+([0-9]+)", response_data) + if not m: + print response_data + die("Filing bug failed") + + self.id = int(m.group(1)) + + print "Successfully created" + print "Bug %d - %s" % (self.id, short_desc) + print "http://%s/show_bug.cgi?id=%d" % (self.host, self.id) + + def create_patch(self, description, comment, filename, data): + fields = {} + fields['bugid'] = str(self.id) + fields['action'] = 'insert' + fields['ispatch'] = '1' + fields['description'] = description + if comment: + fields['comment'] = comment + + files = {} + files['data'] = (filename, 'text/plain', data) + + self._send_post("/attachment.cgi", fields, files) + + response = self.connection.getresponse() + response_data = response.read() + + if response.status != 200: + die ("Failed to attach bug: %d" % response.status) + + print "Attached %s" % filename + + def download_patch(self, patch): + self._send_request("GET", "/attachment.cgi?id=" + str(patch.attach_id)) + + response = self.connection.getresponse() + if response.status != 200: + die ("Failed to download attachment %s: %d" % (patch.attach_id, response.status)) + + return response.read() + + @staticmethod + def load(bug_reference): + (host, https, id) = resolve_bug_reference(bug_reference) + + bug = Bug(host, https) + bug._load(id) + + return bug + + @staticmethod + def create(tracker, product, component, short_desc, comment): + host = resolve_host_alias(tracker) + https = tracker_uses_https(tracker) + default_fields = get_default_fields(tracker) + + bug = Bug(host, https) + bug._create(product, component, short_desc, comment, default_fields) + + return bug + +# The Commands +# ============= + +def do_apply(bug_reference): + bug = Bug.load(bug_reference) + + print "Bug %d - %s" % (bug.id, bug.short_desc) + print + + for patch in bug.patches: + print patch.description + if not prompt("Apply?"): + continue + + print + + patch_contents = bug.download_patch(patch) + handle, filename = tempfile.mkstemp(".patch", make_filename(patch.description) + "-") + f = os.fdopen(handle, "w") + f.write(patch_contents) + f.close() + + process = subprocess.Popen(['git', 'am', filename]) + process.wait() + if process.returncode != 0: + print "Patch left in %s" % filename + break + + os.remove(filename) + +def attach_commits(bug, commits, include_comments=True): + # We want to attach the patches in chronological order + commits = list(commits) + commits.reverse() + + for commit in commits: + filename = make_filename(commit.message) + ".patch" + patch = get_patch(commit) + if include_comments: + body = get_body(commit) + else: + body = None + bug.create_patch(commit.message, body, filename, patch) + +def do_attach(bug_reference, since_or_revision_range): + bug = Bug.load(bug_reference) + + print "Bug %d - %s" % (bug.id, bug.short_desc) + print + + commits = get_commits(since_or_revision_range) + for commit in commits: + print commit.id[0:7], commit.message + + print + if not prompt("Attach?"): + print "Aborting" + sys.exit(0) + + attach_commits(bug, commits) + +def do_file(product_component, since_or_revision_range): + m = re.match("([^/\s]+)/([^/\s]+)", product_component) + if not m: + die("'%s' is not a valid <product>/<component> pair" % product_component) + product = m.group(1) + component = m.group(2) + + commits = get_commits(since_or_revision_range) + + template = StringIO() + if len(commits) == 1: + template.write(commits[0].message) + template.write("\n\n") + template.write(get_body(commits[0])) + template.write("\n") + template.write(""" +# Please enter the summary (first line) and description (other lines). Lines +# starting with '#' will be ignored. Delete everything to abort. +# +# Product: %(product)s +# Component: %(component)s +# Patches to be attached: +""" % { 'product': product, 'component': component }) + for commit in commits: + template.write("# " + commit.id[0:7] + " " + commit.message + "\n") + + handle, filename = tempfile.mkstemp(".txt", "git-bz-") + f = os.fdopen(handle, "w") + f.write(template.getvalue()) + f.close() + + edit(filename) + + f = open(filename, "r") + lines = filter(lambda x: not x.startswith("#"), f.readlines()) + f.close() + + i = 0 + summary = "" + while i < len(lines): + summary = lines[i].strip() + if summary != "": + break + i += 1 + + if summary == "": + die("Empty summary, aborting") + + description = "".join(lines[i + 1:]).strip() + + if global_options.bugzilla: + tracker = global_options.bugzilla + else: + tracker = get_default_tracker() + + bug = Bug.create(tracker, product, component, summary, description) + + attach_commits(bug, commits, include_comments=(len(commits) > 1)) + +################################################################################ + +if len(sys.argv) > 1: + command = sys.argv[1] +else: + command = '' + +sys.argv[1:2] = [] + +parser = OptionParser() + +def add_num_option(): + parser.add_option("", "--num", metavar="N", + help="limit number of patches to attach (can abbreviate to -<N>)") + for i, arg in enumerate(sys.argv): + m = re.match("-([0-9]+)", arg) + if m: + sys.argv[i] = "--num=" + m.group(1) + +if command == 'apply': + parser.set_usage("git bz apply [options] <bug reference>"); + n_args = 1 +elif command == 'attach': + parser.set_usage("git bz attach [-<N>] [options] <bug reference> [<since | <revision range>]"); + add_num_option() + n_args = 2 +elif command == 'file': + parser.set_usage("git bz file [-<N>] [options] <product>/<component> [<since> | <revision range>]"); + parser.add_option("-b", "--bugzilla", metavar="HOST_OR_ALIAS", + help="bug tracker to file bug on") + add_num_option() + n_args = 2 +else: + print >>sys.stderr, "Usage: git bz [apply|attach|file] [options]" + sys.exit(1) + +options, args = parser.parse_args() + +if len(args) != n_args: + parser.print_usage() + sys.exit(1) + +global_repo = git.Repo() + +if command == 'apply': + do_apply(*args) +if command == 'attach': + do_attach(*args) +elif command == 'file': + do_file(*args) + +sys.exit(0) |