.. _changelog:
=========
Changelog
1.4.0 (**2012-XX-XX**)
----------------------
:status: in-progress
:branch: beta
news
++++
- new codereview system
- email map, allowing users to have multiple email addresses mapped into
their accounts
- improved git-hook system. Now all actions for git are logged into journal
including pushed revisions, user and IP address
- changed setup-app into setup-rhodecode and added default options to it.
- new git repos are created as bare now by default
- #464 added links to groups in permission box
- #465 mentions autocomplete inside comments boxes
- #469 added --update-only option to whoosh to re-index only given list
of repos in index
- rhodecode-api CLI client
- new git http protocol replaced buggy dulwich implementation.
Now based on pygrack & gitweb
- Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
reformated based on user suggestions. Additional rss/atom feeds for user
journal
- various i18n improvements
- #478 permissions overview for admin in user edit view
fixes
+++++
- improved translations
- fixes issue #455 Creating an archive generates an exception on Windows
- fixes #448 Download ZIP archive keeps file in /tmp open and results
in out of disk space
- fixes issue #454 Search results under Windows include proceeding
backslash
- fixed issue #450. Rhodecode no longer will crash when bad revision is
present in journal data.
- fix for issue #417, git execution was broken on windows for certain
commands.
- fixed #413. Don't disable .git directory for bare repos on deleting
- fixed issue #459. Changed the way of obtaining logger in reindex task.
- fixed #453 added ID field in whoosh SCHEMA that solves the issue of
reindexing modified files
- fixes #481 rhodecode emails are sent without Date header
1.3.6 (**2012-05-17**)
- chinese traditional translation
- changed setup-app into setup-rhodecode and added arguments for auto-setup
mode that doesn't need user interaction
- fixed no scm found warning
- fixed __future__ import error on rcextensions
- made simplejson required lib for speedup on JSON encoding
- fixes #449 bad regex could get more than revisions from parsing history
- don't clear DB session when CELERY_EAGER is turned ON
1.3.5 (**2012-05-10**)
- use ext_json for json module
- unified annotation view with file source view
- notification improvements, better inbox + css
- #419 don't strip passwords for login forms, make rhodecode
more compatible with LDAP servers
- Added HTTP_X_FORWARDED_FOR as another method of extracting
IP for pull/push logs. - moved all to base controller
- #415: Adding comment to changeset causes reload.
Comments are now added via ajax and doesn't reload the page
- #374 LDAP config is discarded when LDAP can't be activated
- limited push/pull operations are now logged for git in the journal
- bumped mercurial to 2.2.X series
- added support for displaying submodules in file-browser
- #421 added bookmarks in changelog view
- fixed dev-version marker for stable when served from source codes
- fixed missing permission checks on show forks page
- #418 cast to unicode fixes in notification objects
- #426 fixed mention extracting regex
- fixed remote-pulling for git remotes remopositories
- fixed #434: Error when accessing files or changesets of a git repository
with submodules
- fixed issue with empty APIKEYS for users after registration ref. #438
- fixed issue with getting README files from git repositories
1.3.4 (**2012-03-28**)
- Whoosh logging is now controlled by the .ini files logging setup
- added clone-url into edit form on /settings page
- added help text into repo add/edit forms
- created rcextensions module with additional mappings (ref #322) and
post push/pull/create repo hooks callbacks
- implemented #377 Users view for his own permissions on account page
- #399 added inheritance of permissions for users group on repos groups
- #401 repository group is automatically pre-selected when adding repos
inside a repository group
- added alternative HTTP 403 response when client failed to authenticate. Helps
solving issues with Mercurial and LDAP
- #402 removed group prefix from repository name when listing repositories
inside a group
- added gravatars into permission view and permissions autocomplete
- #347 when running multiple RhodeCode instances, properly invalidates cache
for all registered servers
- fixed #390 cache invalidation problems on repos inside group
- fixed #385 clone by ID url was loosing proxy prefix in URL
- fixed some unicode problems with waitress
- fixed issue with escaping < and > in changeset commits
- fixed error occurring during recursive group creation in API
create_repo function
- fixed #393 py2.5 fixes for routes url generator
- fixed #397 Private repository groups shows up before login
- fixed #396 fixed problems with revoking users in nested groups
- fixed mysql unicode issues + specified InnoDB as default engine with
utf8 charset
- #406 trim long branch/tag names in changelog to not break UI
1.3.3 (**2012-03-02**)
from rhodecode.lib.rcmail.response import MailResponse
from rhodecode.lib.rcmail.exceptions import BadHeaders
from rhodecode.lib.rcmail.exceptions import InvalidMessage
class Attachment(object):
"""
Encapsulates file attachment information.
:param filename: filename of attachment
:param content_type: file mimetype
:param data: the raw file data, either as string or file obj
:param disposition: content-disposition (if any)
def __init__(self,
filename=None,
content_type=None,
data=None,
disposition=None):
self.filename = filename
self.content_type = content_type
self.disposition = disposition or 'attachment'
self._data = data
@property
def data(self):
if isinstance(self._data, basestring):
return self._data
self._data = self._data.read()
class Message(object):
Encapsulates an email message.
:param subject: email subject header
:param recipients: list of email addresses
:param body: plain text message
:param html: HTML message
:param sender: email sender address
:param cc: CC list
:param bcc: BCC list
:param extra_headers: dict of extra email headers
:param attachments: list of Attachment instances
:param recipients_separator: alternative separator for any of
'From', 'To', 'Delivered-To', 'Cc', 'Bcc' fields
subject=None,
recipients=None,
body=None,
html=None,
sender=None,
cc=None,
bcc=None,
extra_headers=None,
attachments=None,
recipients_separator="; "):
self.subject = subject or ''
self.sender = sender
self.body = body
self.html = html
self.recipients = recipients or []
self.attachments = attachments or []
self.cc = cc or []
self.bcc = bcc or []
self.extra_headers = extra_headers or {}
self.recipients_separator = recipients_separator
def send_to(self):
return set(self.recipients) | set(self.bcc or ()) | set(self.cc or ())
def to_message(self):
Returns raw email.Message instance.Validates message first.
self.validate()
return self.get_response().to_message()
def get_response(self):
Creates a Lamson MailResponse instance
response = MailResponse(Subject=self.subject,
To=self.recipients,
From=self.sender,
Body=self.body,
Html=self.html,
separator=self.recipients_separator)
if self.cc:
response.base['Cc'] = self.cc
for attachment in self.attachments:
response.attach(attachment.filename,
attachment.content_type,
attachment.data,
attachment.disposition)
response.update(self.extra_headers)
return response
def is_bad_headers(self):
Checks for bad headers i.e. newlines in subject, sender or recipients.
headers = [self.subject, self.sender]
headers += list(self.send_to)
headers += self.extra_headers.values()
for val in headers:
for c in '\r\n':
if c in val:
return True
return False
def validate(self):
Checks if message is valid and raises appropriate exception.
if not self.recipients:
raise InvalidMessage, "No recipients have been added"
raise InvalidMessage("No recipients have been added")
if not self.body and not self.html:
raise InvalidMessage, "No body has been set"
raise InvalidMessage("No body has been set")
if not self.sender:
raise InvalidMessage, "No sender address has been set"
raise InvalidMessage("No sender address has been set")
if self.is_bad_headers():
raise BadHeaders
def add_recipient(self, recipient):
Adds another recipient to the message.
:param recipient: email address of recipient.
self.recipients.append(recipient)
def add_cc(self, recipient):
Adds an email address to the CC list.
self.cc.append(recipient)
def add_bcc(self, recipient):
Adds an email address to the BCC list.
self.bcc.append(recipient)
def attach(self, attachment):
Adds an attachment to the message.
:param attachment: an **Attachment** instance.
self.attachments.append(attachment)
@@ -271,179 +271,183 @@ class MailResponse(object):
Figures out all the required steps to finally craft the
message you need and return it. The resulting message
is also available as a self.base attribute.
What is returned is a Python email API message you can
use with those APIs. The self.base attribute is the raw
lamson.encoding.MailBase.
del self.base.parts[:]
if self.Body and self.Html:
self.multipart = True
self.base.content_encoding['Content-Type'] = (
'multipart/alternative', {})
if self.multipart:
self.base.body = None
if self.Body:
self.base.attach_text(self.Body, 'text/plain')
if self.Html:
self.base.attach_text(self.Html, 'text/html')
for args in self.attachments:
self._encode_attachment(**args)
elif self.Body:
self.base.body = self.Body
self.base.content_encoding['Content-Type'] = ('text/plain', {})
elif self.Html:
self.base.body = self.Html
self.base.content_encoding['Content-Type'] = ('text/html', {})
return to_message(self.base, separator=self.separator)
def all_parts(self):
Returns all the encoded parts. Only useful for debugging
or inspecting after calling to_message().
return self.base.parts
def keys(self):
return self.base.keys()
def to_message(mail, separator="; "):
Given a MailBase message, this will construct a MIMEPart
that is canonicalized for use with the Python email API.
ctype, params = mail.content_encoding['Content-Type']
if not ctype:
if mail.parts:
ctype = 'multipart/mixed'
else:
ctype = 'text/plain'
assert ctype.startswith(("multipart", "message")), \
"Content type should be multipart or message, not %r" % ctype
# adjust the content type according to what it should be now
mail.content_encoding['Content-Type'] = (ctype, params)
try:
out = MIMEPart(ctype, **params)
except TypeError, exc: # pragma: no cover
raise EncodingError("Content-Type malformed, not allowed: %r; "
"%r (Python ERROR: %s" %
(ctype, params, exc.message))
for k in mail.keys():
if k in ADDRESS_HEADERS_WHITELIST:
out[k.encode('ascii')] = header_to_mime_encoding(
mail[k],
not_email=False,
separator=separator
)
not_email=True
out.extract_payload(mail)
# go through the children
for part in mail.parts:
out.attach(to_message(part))
return out
class MIMEPart(MIMEBase):
A reimplementation of nearly everything in email.mime to be more useful
for actually attaching things. Rather than one class for every type of
thing you'd encode, there's just this one, and it figures out how to
encode what you ask it.
def __init__(self, type, **params):
self.maintype, self.subtype = type.split('/')
MIMEBase.__init__(self, self.maintype, self.subtype, **params)
def add_text(self, content):
# this is text, so encode it in canonical form
encoded = content.encode('ascii')
charset = 'ascii'
except UnicodeError:
encoded = content.encode('utf-8')
charset = 'utf-8'
self.set_payload(encoded, charset=charset)
def extract_payload(self, mail):
if mail.body == None: return # only None, '' is still ok
if mail.body == None:
return # only None, '' is still ok
ctype, ctype_params = mail.content_encoding['Content-Type']
cdisp, cdisp_params = mail.content_encoding['Content-Disposition']
assert ctype, ("Extract payload requires that mail.content_encoding "
"have a valid Content-Type.")
if ctype.startswith("text/"):
self.add_text(mail.body)
if cdisp:
# replicate the content-disposition settings
self.add_header('Content-Disposition', cdisp, **cdisp_params)
self.set_payload(mail.body)
encoders.encode_base64(self)
def __repr__(self):
return "<MIMEPart '%s/%s': %r, %r, multipart=%r>" % (
self.subtype,
self.maintype,
self['Content-Type'],
self['Content-Disposition'],
self.is_multipart())
def header_to_mime_encoding(value, not_email=False, separator=", "):
if not value: return ""
if not value:
return ""
encoder = Charset(DEFAULT_ENCODING)
if type(value) == list:
return separator.join(properly_encode_header(
v, encoder, not_email) for v in value)
return properly_encode_header(value, encoder, not_email)
def properly_encode_header(value, encoder, not_email):
The only thing special (weird) about this function is that it tries
to do a fast check to see if the header value has an email address in
it. Since random headers could have an email address, and email addresses
have weird special formatting rules, we have to check for it.
Normally this works fine, but in Librelist, we need to "obfuscate" email
addresses by changing the '@' to '-AT-'. This is where
VALUE_IS_EMAIL_ADDRESS exists. It's a simple lambda returning True/False
to check if a header value has an email address. If you need to make this
check different, then change this.
return value.encode("ascii")
except UnicodeEncodeError:
if not_email is False and VALUE_IS_EMAIL_ADDRESS(value):
# this could have an email address, make sure we don't screw it up
name, address = parseaddr(value)
return '"%s" <%s>' % (
encoder.header_encode(name.encode("utf-8")), address)
return encoder.header_encode(value.encode("utf-8"))
# -*- coding: utf-8 -*-
rhodecode.lib.rcmail.smtp_mailer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Simple smtp mailer used in RhodeCode
:created_on: Sep 13, 2010
:copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
:license: GPLv3, see COPYING for more details.
# 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 3 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, see <http://www.gnu.org/licenses/>.
import time
import logging
import smtplib
from socket import sslerror
from email.utils import formatdate
from rhodecode.lib.rcmail.message import Message
class SmtpMailer(object):
"""SMTP mailer class
mailer = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth
mail_port, ssl, tls)
mailer.send(recipients, subject, body, attachment_files)
:param recipients might be a list of string or single string
:param attachment_files is a dict of {filename:location}
it tries to guess the mimetype and attach the file
def __init__(self, mail_from, user, passwd, mail_server, smtp_auth=None,
mail_port=None, ssl=False, tls=False, debug=False):
self.mail_from = mail_from
self.mail_server = mail_server
self.mail_port = mail_port
self.user = user
self.passwd = passwd
self.ssl = ssl
self.tls = tls
self.debug = debug
self.auth = smtp_auth
def send(self, recipients=[], subject='', body='', html='',
attachment_files=None):
if isinstance(recipients, basestring):
recipients = [recipients]
headers = {
'Date': formatdate(time.time())
}
msg = Message(subject, recipients, body, html, self.mail_from,
recipients_separator=", ")
recipients_separator=", ", extra_headers=headers)
raw_msg = msg.to_message()
if self.ssl:
smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
if self.tls:
smtp_serv.ehlo()
smtp_serv.starttls()
if self.debug:
smtp_serv.set_debuglevel(1)
if self.auth:
smtp_serv.esmtp_features["auth"] = self.auth
# if server requires authorization you must provide login and password
# but only if we have them
if self.user and self.passwd:
smtp_serv.login(self.user, self.passwd)
smtp_serv.sendmail(msg.sender, msg.send_to, raw_msg.as_string())
logging.info('MAIL SEND TO: %s' % recipients)
smtp_serv.quit()
except sslerror:
# sslerror is raised in tls connections on closing sometimes
pass
Status change: