.. _changelog:
=========
Changelog
1.4.0 (**2012-XX-XX**)
----------------------
:status: in-progress
:branch: beta
news
++++
fixes
+++++
1.3.6 (**2012-05-16**)
- chinese traditional translation
- 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
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**)
- fixed some python2.5 compatibility issues
- fixed issues with removed repos was accidentally added as groups, after
full rescan of paths
- fixes #376 Cannot edit user (using container auth)
- fixes #378 Invalid image urls on changeset screen with proxy-prefix
configuration
- fixed initial sorting of repos inside repo group
- fixes issue when user tried to resubmit same permission into user/user_groups
- bumped beaker version that fixes #375 leap error bug
- fixed raw_changeset for git. It was generated with hg patch headers
- fixed vcs issue with last_changeset for filenodes
- fixed missing commit after hook delete
- fixed #372 issues with git operation detection that caused a security issue
for git repos
1.3.2 (**2012-02-28**)
- fixed git protocol issues with repos-groups
- fixed git remote repos validator that prevented from cloning remote git repos
- fixes #370 ending slashes fixes for repo and groups
- fixes #368 improved git-protocol detection to handle other clients
- fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
Moved To Root
- fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
- fixed #373 missing cascade drop on user_group_to_perm table
1.3.1 (**2012-02-27**)
- redirection loop occurs when remember-me wasn't checked during login
- fixes issues with git blob history generation
- don't fetch branch for git in file history dropdown. Causes unneeded slowness
1.3.0 (**2012-02-26**)
- code review, inspired by github code-comments
- #215 rst and markdown README files support
- #252 Container-based and proxy pass-through authentication support
- #44 branch browser. Filtering of changelog by branches
- mercurial bookmarks support
- new hover top menu, optimized to add maximum size for important views
- configurable clone url template with possibility to specify protocol like
ssh:// or http:// and also manually alter other parts of clone_url.
- enabled largefiles extension by default
- optimized summary file pages and saved a lot of unused space in them
- #239 option to manually mark repository as fork
- #320 mapping of commit authors to RhodeCode users
- #304 hashes are displayed using monospace font
- diff configuration, toggle white lines and context lines
- #307 configurable diffs, whitespace toggle, increasing context lines
- sorting on branches, tags and bookmarks using YUI datatable
- improved file filter on files page
- implements #330 api method for listing nodes ar particular revision
- #73 added linking issues in commit messages to chosen issue tracker url
based on user defined regular expression
- added linking of changesets in commit messages
- new compact changelog with expandable commit messages
- firstname and lastname are optional in user creation
- #348 added post-create repository hook
- #212 global encoding settings is now configurable from .ini files
- #227 added repository groups permissions
- markdown gets codehilite extensions
- new API methods, delete_repositories, grante/revoke permissions for groups
and repos
- rewrote dbsession management for atomic operations, and better error handling
- fixed sorting of repo tables
- #326 escape of special html entities in diffs
- normalized user_name => username in api attributes
- fixes #298 ldap created users with mixed case emails created conflicts
on saving a form
- fixes issue when owner of a repo couldn't revoke permissions for users
and groups
- fixes #271 rare JSON serialization problem with statistics
- fixes #337 missing validation check for conflicting names of a group with a
repositories group
- #340 fixed session problem for mysql and celery tasks
- fixed #331 RhodeCode mangles repository names if the a repository group
contains the "full path" to the repositories
- #355 RhodeCode doesn't store encrypted LDAP passwords
1.2.5 (**2012-01-28**)
@@ -51,389 +51,389 @@ class GitChangeset(BaseChangeset):
@LazyProperty
def author(self):
return safe_unicode(self._commit.committer)
def date(self):
return date_fromtimestamp(self._commit.commit_time,
self._commit.commit_timezone)
def status(self):
"""
Returns modified, added, removed, deleted files for current changeset
return self.changed, self.added, self.removed
def branch(self):
heads = self.repository._heads(reverse=False)
ref = heads.get(self.raw_id)
if ref:
return safe_unicode(ref)
def _fix_path(self, path):
Paths are stored without trailing slash so we need to get rid off it if
needed.
if path.endswith('/'):
path = path.rstrip('/')
return path
def _get_id_for_path(self, path):
# FIXME: Please, spare a couple of minutes and make those codes cleaner;
if not path in self._paths:
path = path.strip('/')
# set root tree
tree = self.repository._repo[self._commit.tree]
if path == '':
self._paths[''] = tree.id
return tree.id
splitted = path.split('/')
dirs, name = splitted[:-1], splitted[-1]
curdir = ''
# initially extract things from root dir
for item, stat, id in tree.iteritems():
if curdir:
name = '/'.join((curdir, item))
else:
name = item
self._paths[name] = id
self._stat_modes[name] = stat
for dir in dirs:
curdir = '/'.join((curdir, dir))
curdir = dir
dir_id = None
if dir == item:
dir_id = id
if dir_id:
# Update tree
tree = self.repository._repo[dir_id]
if not isinstance(tree, objects.Tree):
raise ChangesetError('%s is not a directory' % curdir)
raise ChangesetError('%s have not been found' % curdir)
# cache all items from the given traversed tree
raise NodeDoesNotExistError("There is no file nor directory "
"at the given path %r at revision %r"
% (path, self.short_id))
return self._paths[path]
def _get_kind(self, path):
id = self._get_id_for_path(path)
obj = self.repository._repo[id]
if isinstance(obj, objects.Blob):
return NodeKind.FILE
elif isinstance(obj, objects.Tree):
return NodeKind.DIR
def _get_file_nodes(self):
return chain(*(t[2] for t in self.walk()))
def parents(self):
Returns list of parents changesets.
return [self.repository.get_changeset(parent)
for parent in self._commit.parents]
def next(self, branch=None):
if branch and self.branch != branch:
raise VCSError('Branch option used on changeset not belonging '
'to that branch')
def _next(changeset, branch):
try:
next_ = changeset.revision + 1
next_rev = changeset.repository.revisions[next_]
except IndexError:
raise ChangesetDoesNotExistError
cs = changeset.repository.get_changeset(next_rev)
if branch and branch != cs.branch:
return _next(cs, branch)
return cs
return _next(self, branch)
def prev(self, branch=None):
def _prev(changeset, branch):
prev_ = changeset.revision - 1
if prev_ < 0:
raise IndexError
prev_rev = changeset.repository.revisions[prev_]
cs = changeset.repository.get_changeset(prev_rev)
return _prev(cs, branch)
return _prev(self, branch)
def get_file_mode(self, path):
Returns stat mode of the file at the given ``path``.
# ensure path is traversed
self._get_id_for_path(path)
return self._stat_modes[path]
def get_file_content(self, path):
Returns content of the file at given ``path``.
blob = self.repository._repo[id]
return blob.as_pretty_string()
def get_file_size(self, path):
Returns size of the file at given ``path``.
return blob.raw_length()
def get_file_changeset(self, path):
Returns last commit of the file at the given ``path``.
node = self.get_node(path)
return node.history[0]
def get_file_history(self, path):
Returns history of file as reversed list of ``Changeset`` objects for
which file at given ``path`` has been modified.
TODO: This function now uses os underlying 'git' and 'grep' commands
which is generally not good. Should be replaced with algorithm
iterating commits.
cmd = 'log --pretty="format: %%H" --name-status -p %s -- "%s"' % (
cmd = 'log --pretty="format: --%%H--" --name-status -p %s -- "%s"' % (
self.id, path
)
so, se = self.repository.run_git_command(cmd)
ids = re.findall(r'\w{40}', so)
ids = re.findall(r'(?:--)(\w{40})(?:--)', so)
return [self.repository.get_changeset(id) for id in ids]
def get_file_annotate(self, path):
Returns a list of three element tuples with lineno,changeset and line
TODO: This function now uses os underlying 'git' command which is
generally not good. Should be replaced with algorithm iterating
commits.
cmd = 'blame -l --root -r %s -- "%s"' % (self.id, path)
# -l ==> outputs long shas (and we need all 40 characters)
# --root ==> doesn't put '^' character for bounderies
# -r sha ==> blames for the given revision
annotate = []
for i, blame_line in enumerate(so.split('\n')[:-1]):
ln_no = i + 1
id, line = re.split(r' \(.+?\) ', blame_line, 1)
annotate.append((ln_no, self.repository.get_changeset(id), line))
return annotate
def fill_archive(self, stream=None, kind='tgz', prefix=None,
subrepos=False):
Fills up given stream.
:param stream: file like object.
:param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
Default: ``tgz``.
:param prefix: name of root directory in archive.
Default is repository name and changeset's raw_id joined with dash
(``repo-tip.<KIND>``).
:param subrepos: include subrepos in this archive.
:raise ImproperArchiveTypeError: If given kind is wrong.
:raise VcsError: If given stream is None
allowed_kinds = settings.ARCHIVE_SPECS.keys()
if kind not in allowed_kinds:
raise ImproperArchiveTypeError('Archive kind not supported use one'
'of %s', allowed_kinds)
if prefix is None:
prefix = '%s-%s' % (self.repository.name, self.short_id)
elif prefix.startswith('/'):
raise VCSError("Prefix cannot start with leading slash")
elif prefix.strip() == '':
raise VCSError("Prefix cannot be empty")
if kind == 'zip':
frmt = 'zip'
frmt = 'tar'
cmd = 'git archive --format=%s --prefix=%s/ %s' % (frmt, prefix,
self.raw_id)
if kind == 'tgz':
cmd += ' | gzip -9'
elif kind == 'tbz2':
cmd += ' | bzip2 -9'
if stream is None:
raise VCSError('You need to pass in a valid stream for filling'
' with archival data')
popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
cwd=self.repository.path)
buffer_size = 1024 * 8
chunk = popen.stdout.read(buffer_size)
while chunk:
stream.write(chunk)
# Make sure all descriptors would be read
popen.communicate()
def get_nodes(self, path):
if self._get_kind(path) != NodeKind.DIR:
raise ChangesetError("Directory does not exist for revision %r at "
" %r" % (self.revision, path))
path = self._fix_path(path)
tree = self.repository._repo[id]
dirnodes = []
filenodes = []
als = self.repository.alias
for name, stat, id in tree.iteritems():
if objects.S_ISGITLINK(stat):
dirnodes.append(SubModuleNode(name, url=None, changeset=id,
alias=als))
continue
obj = self.repository._repo.get_object(id)
if path != '':
obj_path = '/'.join((path, name))
obj_path = name
if obj_path not in self._stat_modes:
self._stat_modes[obj_path] = stat
if isinstance(obj, objects.Tree):
dirnodes.append(DirNode(obj_path, changeset=self))
elif isinstance(obj, objects.Blob):
filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
raise ChangesetError("Requested object should be Tree "
"or Blob, is %r" % type(obj))
nodes = dirnodes + filenodes
for node in nodes:
if not node.path in self.nodes:
self.nodes[node.path] = node
nodes.sort()
return nodes
def get_node(self, path):
if isinstance(path, unicode):
path = path.encode('utf-8')
if not path in self.nodes:
id_ = self._get_id_for_path(path)
except ChangesetError:
raise NodeDoesNotExistError("Cannot find one of parents' "
"directories for a given path: %s" % path)
_GL = lambda m: m and objects.S_ISGITLINK(m)
if _GL(self._stat_modes.get(path)):
node = SubModuleNode(path, url=None, changeset=id_, alias=als)
obj = self.repository._repo.get_object(id_)
node = RootNode(changeset=self)
node = DirNode(path, changeset=self)
node._tree = obj
node = FileNode(path, changeset=self)
node._blob = obj
# cache node
self.nodes[path] = node
return self.nodes[path]
def affected_files(self):
Get's a fast accessible file changes for given changeset
return self.added + self.changed
def _diff_name_status(self):
output = []
for parent in self.parents:
cmd = 'diff --name-status %s %s --encoding=utf8' % (parent.raw_id, self.raw_id)
output.append(so.strip())
return '\n'.join(output)
def _get_paths_for_status(self, status):
Returns sorted list of paths for given ``status``.
:param status: one of: *added*, *modified* or *deleted*
paths = set()
char = status[0].upper()
for line in self._diff_name_status.splitlines():
if not line:
if line.startswith(char):
splitted = line.split(char, 1)
if not len(splitted) == 2:
raise VCSError("Couldn't parse diff result:\n%s\n\n and "
"particularly that line: %s" % (self._diff_name_status,
line))
_path = splitted[1].strip()
paths.add(_path)
return sorted(paths)
def added(self):
Returns list of added ``FileNode`` objects.
Status change: