Modified: incubator/bloodhound/trunk/trac/tracopt/ticket/deleter.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/tracopt/ticket/deleter.py?rev=1398968&r1=1398967&r2=1398968&view=diff ============================================================================== --- incubator/bloodhound/trunk/trac/tracopt/ticket/deleter.py (original) +++ incubator/bloodhound/trunk/trac/tracopt/ticket/deleter.py Tue Oct 16 20:06:09 2012 @@ -20,6 +20,7 @@ from trac.ticket.model import Ticket from trac.ticket.web_ui import TicketModule from trac.util import get_reporter_id from trac.util.datefmt import from_utimestamp +from trac.util.presentation import captioned_button from trac.util.translation import _ from trac.web.api import IRequestFilter, IRequestHandler, ITemplateStreamFilter from trac.web.chrome import ITemplateProvider, add_notice, add_stylesheet @@ -67,9 +68,12 @@ class TicketDeleter(Component): return tag.form( tag.div( tag.input(type='hidden', name='action', value='delete'), - tag.input(type='submit', value=_('Delete'), - title=_('Delete ticket')), - class_='inlinebuttons'), + tag.input(type='submit', + value=captioned_button(req, u'â', # 'EN DASH' + _("Delete")), + title=_('Delete ticket'), + class_="trac-delete"), + class_="inlinebuttons"), action='#', method='get') def delete_comment(): @@ -81,10 +85,12 @@ class TicketDeleter(Component): value='delete-comment'), tag.input(type='hidden', name='cnum', value=cnum), tag.input(type='hidden', name='cdate', value=cdate), - tag.input(type='submit', value=_('Delete'), - title=_('Delete comment %(num)s', - num=cnum)), - class_='inlinebuttons'), + tag.input(type='submit', + value=captioned_button(req, u'â', # 'EN DASH' + _("Delete")), + title=_('Delete comment %(num)s', num=cnum), + class_="trac-delete"), + class_="inlinebuttons"), action='#', method='get') buffer = StreamBuffer()
Modified: incubator/bloodhound/trunk/trac/tracopt/versioncontrol/git/PyGIT.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/tracopt/versioncontrol/git/PyGIT.py?rev=1398968&r1=1398967&r2=1398968&view=diff ============================================================================== --- incubator/bloodhound/trunk/trac/tracopt/versioncontrol/git/PyGIT.py (original) +++ incubator/bloodhound/trunk/trac/tracopt/versioncontrol/git/PyGIT.py Tue Oct 16 20:06:09 2012 @@ -31,12 +31,70 @@ import weakref __all__ = ['GitError', 'GitErrorSha', 'Storage', 'StorageFactory'] + +def terminate(process): + """Python 2.5 compatibility method. + os.kill is not available on Windows before Python 2.7. + In Python 2.6 subprocess.Popen has a terminate method. + (It also seems to have some issues on Windows though.) + """ + + def terminate_win(process): + import ctypes + PROCESS_TERMINATE = 1 + handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, + False, + process.pid) + ctypes.windll.kernel32.TerminateProcess(handle, -1) + ctypes.windll.kernel32.CloseHandle(handle) + + def terminate_nix(process): + import os + import signal + return os.kill(process.pid, signal.SIGTERM) + + if sys.platform == 'win32': + return terminate_win(process) + return terminate_nix(process) + + class GitError(Exception): pass class GitErrorSha(GitError): pass +# Helper functions + +def parse_commit(raw): + """Parse the raw content of a commit (as given by `git cat-file -p <rev>`). + + Return the commit message and a dict of properties. + """ + if not raw: + raise GitErrorSha + lines = raw.splitlines() + if not lines: + raise GitErrorSha + line = lines.pop(0) + props = {} + multiline = multiline_key = None + while line: + if line[0] == ' ': + if not multiline: + multiline_key = key + multiline = [props[multiline_key][-1]] + multiline.append(line[1:]) + else: + key, value = line.split(None, 1) + props.setdefault(key, []).append(value.strip()) + line = lines.pop(0) + if multiline and (not line or key != multiline_key): + props[multiline_key][-1] = '\n'.join(multiline) + multiline = None + return '\n'.join(lines), props + + class GitCore(object): """Low-level wrapper around git executable""" @@ -252,7 +310,7 @@ class Storage(object): try: g = GitCore(git_bin=git_bin) [v] = g.version().splitlines() - _, _, version = v.strip().split() + version = v.strip().split()[2] # 'version' has usually at least 3 numeric version # components, e.g.:: # 1.5.4.2 @@ -299,6 +357,19 @@ class Storage(object): self.logger = log + self.commit_encoding = None + + # caches + self.__rev_cache = None + self.__rev_cache_lock = Lock() + + # cache the last 200 commit messages + self.__commit_msg_cache = SizedDict(200) + self.__commit_msg_lock = Lock() + + self.__cat_file_pipe = None + self.__cat_file_pipe_lock = Lock() + if git_fs_encoding is not None: # validate encoding name codecs.lookup(git_fs_encoding) @@ -318,31 +389,21 @@ class Storage(object): self.logger.error("GIT control files missing in '%s'" % git_dir) if os.path.exists(__git_file_path('.git')): self.logger.error("entry '.git' found in '%s'" - " -- maybe use that folder instead..." + " -- maybe use that folder instead..." % git_dir) raise GitError("GIT control files not found, maybe wrong " "directory?") - self.logger.debug("PyGIT.Storage instance %d constructed" % id(self)) - self.repo = GitCore(git_dir, git_bin=git_bin) - self.commit_encoding = None - - # caches - self.__rev_cache = None - self.__rev_cache_lock = Lock() - - # cache the last 200 commit messages - self.__commit_msg_cache = SizedDict(200) - self.__commit_msg_lock = Lock() - - self.__cat_file_pipe = None + self.logger.debug("PyGIT.Storage instance %d constructed" % id(self)) def __del__(self): - if self.__cat_file_pipe is not None: - self.__cat_file_pipe.stdin.close() - self.__cat_file_pipe.wait() + with self.__cat_file_pipe_lock: + if self.__cat_file_pipe is not None: + self.__cat_file_pipe.stdin.close() + terminate(self.__cat_file_pipe) + self.__cat_file_pipe.wait() # # cache handling @@ -587,20 +648,39 @@ class Storage(object): return self.verifyrev('HEAD') def cat_file(self, kind, sha): - if self.__cat_file_pipe is None: - self.__cat_file_pipe = self.repo.cat_file_batch() - - self.__cat_file_pipe.stdin.write(sha + '\n') - self.__cat_file_pipe.stdin.flush() - _sha, _type, _size = self.__cat_file_pipe.stdout.readline().split() - - if _type != kind: - raise TracError("internal error (got unexpected object kind " - "'%s')" % k) - - size = int(_size) - return self.__cat_file_pipe.stdout.read(size + 1)[:size] + with self.__cat_file_pipe_lock: + if self.__cat_file_pipe is None: + self.__cat_file_pipe = self.repo.cat_file_batch() + try: + self.__cat_file_pipe.stdin.write(sha + '\n') + self.__cat_file_pipe.stdin.flush() + + split_stdout_line = self.__cat_file_pipe.stdout.readline() \ + .split() + if len(split_stdout_line) != 3: + raise GitError("internal error (could not split line " + "'%s')" % (split_stdout_line,)) + + _sha, _type, _size = split_stdout_line + + if _type != kind: + raise GitError("internal error (got unexpected object " + "kind '%s', expected '%s')" + % (_type, kind)) + + size = int(_size) + return self.__cat_file_pipe.stdout.read(size + 1)[:size] + except: + # There was an error, we should close the pipe to get to a + # consistent state (Otherwise it happens that next time we + # call cat_file we get payload from previous call) + self.logger.debug("closing cat_file pipe") + self.__cat_file_pipe.stdin.close() + terminate(self.__cat_file_pipe) + self.__cat_file_pipe.wait() + self.__cat_file_pipe = None + def verifyrev(self, rev): """verify/lookup given revision object and return a sha id or None if lookup failed @@ -737,19 +817,7 @@ class Storage(object): # cache miss raw = self.cat_file('commit', commit_id) raw = unicode(raw, self.get_commit_encoding(), 'replace') - lines = raw.splitlines() - - if not lines: - raise GitErrorSha - - line = lines.pop(0) - props = {} - while line: - key, value = line.split(None, 1) - props.setdefault(key, []).append(value.strip()) - line = lines.pop(0) - - result = ('\n'.join(lines), props) + result = parse_commit(raw) self.__commit_msg_cache[commit_id] = result @@ -821,31 +889,6 @@ class Storage(object): change = {} next_path = [] - def terminate(process): - """Python 2.5 compatibility method. - os.kill is not available on Windows before Python 2.7. - In Python 2.6 subprocess.Popen has a terminate method. - (It also seems to have some issues on Windows though.) - """ - - def terminate_win(process): - import ctypes - PROCESS_TERMINATE = 1 - handle = ctypes.windll.kernel32.OpenProcess(PROCESS_TERMINATE, - False, - process.pid) - ctypes.windll.kernel32.TerminateProcess(handle, -1) - ctypes.windll.kernel32.CloseHandle(handle) - - def terminate_nix(process): - import os - import signal - return os.kill(process.pid, signal.SIGTERM) - - if sys.platform == 'win32': - return terminate_win(process) - return terminate_nix(process) - def name_status_gen(): p[:] = [self.repo.log_pipe('--pretty=format:%n%H', '--name-status', sha, '--', base_path)] Modified: incubator/bloodhound/trunk/trac/tracopt/versioncontrol/git/git_fs.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/tracopt/versioncontrol/git/git_fs.py?rev=1398968&r1=1398967&r2=1398968&view=diff ============================================================================== --- incubator/bloodhound/trunk/trac/tracopt/versioncontrol/git/git_fs.py (original) +++ incubator/bloodhound/trunk/trac/tracopt/versioncontrol/git/git_fs.py Tue Oct 16 20:06:09 2012 @@ -37,7 +37,7 @@ from tracopt.versioncontrol.git import P class GitCachedRepository(CachedRepository): - """Git-specific cached repository + """Git-specific cached repository. Passes through {display,short,normalize}_rev """ @@ -61,7 +61,7 @@ class GitCachedRepository(CachedReposito class GitCachedChangeset(CachedChangeset): - """Git-specific cached changeset + """Git-specific cached changeset. Handles get_branches() """ @@ -85,7 +85,7 @@ def intersperse(sep, iterable): """The 'intersperse' generator takes an element and an iterable and intersperses that element between the elements of the iterable. - inspired by Haskell's Data.List.intersperse + inspired by Haskell's ``Data.List.intersperse`` """ for i, item in enumerate(iterable): @@ -94,12 +94,12 @@ def intersperse(sep, iterable): # helper def _parse_user_time(s): - """parse author/committer attribute lines and return - (user,timestamp) + """Parse author or committer attribute lines and return + corresponding ``(user, timestamp)`` pair. """ user, time, tz_str = s.rsplit(None, 2) - tz = FixedOffset((int(tz_str)*6)/10, tz_str) + tz = FixedOffset((int(tz_str) * 6) / 10, tz_str) time = datetime.fromtimestamp(float(time), tz) return user, time @@ -112,7 +112,7 @@ class GitConnector(Component): self._version = None try: - self._version = PyGIT.Storage.git_version(git_bin=self._git_bin) + self._version = PyGIT.Storage.git_version(git_bin=self.git_bin) except PyGIT.GitError, e: self.log.error("GitError: " + str(e)) @@ -155,7 +155,7 @@ class GitConnector(Component): title=to_unicode(e), rel='nofollow') def get_wiki_syntax(self): - yield (r'(?:\b|!)r?[0-9a-fA-F]{%d,40}\b' % self._wiki_shortrev_len, + yield (r'(?:\b|!)r?[0-9a-fA-F]{%d,40}\b' % self.wiki_shortrev_len, lambda fmt, sha, match: self._format_sha_link(fmt, sha.startswith('r') and sha[1:] or sha, sha)) @@ -166,40 +166,42 @@ class GitConnector(Component): # IRepositoryConnector methods - _persistent_cache = BoolOption('git', 'persistent_cache', 'false', - """enable persistent caching of commit tree""") + persistent_cache = BoolOption('git', 'persistent_cache', 'false', + """Enable persistent caching of commit tree.""") - _cached_repository = BoolOption('git', 'cached_repository', 'false', - """wrap `GitRepository` in `CachedRepository`""") + cached_repository = BoolOption('git', 'cached_repository', 'false', + """Wrap `GitRepository` in `CachedRepository`.""") - _shortrev_len = IntOption('git', 'shortrev_len', 7, - """length rev sha sums should be tried to be abbreviated to - (must be >= 4 and <= 40) + shortrev_len = IntOption('git', 'shortrev_len', 7, + """The length at which a sha1 should be abbreviated to (must + be >= 4 and <= 40). """) - _wiki_shortrev_len = IntOption('git', 'wiki_shortrev_len', 40, - """minimum length of hex-string for which auto-detection as sha id is - performed. - (must be >= 4 and <= 40) - """) - - _trac_user_rlookup = BoolOption('git', 'trac_user_rlookup', 'false', - """enable reverse mapping of git email addresses to trac user ids""") + wiki_shortrev_len = IntOption('git', 'wikishortrev_len', 40, + """The minimum length of an hex-string for which + auto-detection as sha1 is performed (must be >= 4 and <= 40). + """) - _use_committer_id = BoolOption('git', 'use_committer_id', 'true', - """use git-committer id instead of git-author id as changeset owner + trac_user_rlookup = BoolOption('git', 'trac_user_rlookup', 'false', + """Enable reverse mapping of git email addresses to trac user ids + (costly if you have many users).""") + + use_committer_id = BoolOption('git', 'use_committer_id', 'true', + """Use git-committer id instead of git-author id for the + changeset ''Author'' field. """) - _use_committer_time = BoolOption('git', 'use_committer_time', 'true', - """use git-committer-author timestamp instead of git-author timestamp - as changeset timestamp + use_committer_time = BoolOption('git', 'use_committer_time', 'true', + """Use git-committer timestamp instead of git-author timestamp + for the changeset ''Timestamp'' field. """) - _git_fs_encoding = Option('git', 'git_fs_encoding', 'utf-8', - """define charset encoding of paths within git repository""") + git_fs_encoding = Option('git', 'git_fs_encoding', 'utf-8', + """Define charset encoding of paths within git repositories.""") - _git_bin = PathOption('git', 'git_bin', '/usr/bin/git', - """path to git executable (relative to trac project folder!)""") + git_bin = PathOption('git', 'git_bin', '/usr/bin/git', + """Path to git executable (relative to the Trac configuration folder, + so better use an absolute path here).""") def get_supported_types(self): @@ -209,11 +211,11 @@ class GitConnector(Component): """GitRepository factory method""" assert type == 'git' - if not (4 <= self._shortrev_len <= 40): - raise TracError("shortrev_len must be withing [4..40]") + if not (4 <= self.shortrev_len <= 40): + raise TracError("[git] shortrev_len setting must be within [4..40]") - if not (4 <= self._wiki_shortrev_len <= 40): - raise TracError("wiki_shortrev_len must be withing [4..40]") + if not (4 <= self.wiki_shortrev_len <= 40): + raise TracError("[git] wikishortrev_len must be within [4..40]") if not self._version: raise TracError("GIT backend not available") @@ -223,12 +225,12 @@ class GitConnector(Component): (self._version['v_str'], self._version['v_min_str'])) - if self._trac_user_rlookup: + if self.trac_user_rlookup: def rlookup_uid(email): - """reverse map 'real name <u...@domain.tld>' addresses to trac - user ids + """Reverse map 'real name <u...@domain.tld>' addresses to trac + user ids. - returns None if lookup failed + :return: `None` if lookup failed """ try: @@ -250,16 +252,16 @@ class GitConnector(Component): return None repos = GitRepository(dir, params, self.log, - persistent_cache=self._persistent_cache, - git_bin=self._git_bin, - git_fs_encoding=self._git_fs_encoding, - shortrev_len=self._shortrev_len, + persistent_cache=self.persistent_cache, + git_bin=self.git_bin, + git_fs_encoding=self.git_fs_encoding, + shortrev_len=self.shortrev_len, rlookup_uid=rlookup_uid, - use_committer_id=self._use_committer_id, - use_committer_time=self._use_committer_time, + use_committer_id=self.use_committer_id, + use_committer_time=self.use_committer_time, ) - if self._cached_repository: + if self.cached_repository: repos = GitCachedRepository(self.env, repos, self.log) self.log.debug("enabled CachedRepository for '%s'" % dir) else: @@ -369,15 +371,19 @@ class GitRepository(Repository): self.logger = log self.gitrepo = path self.params = params - self._shortrev_len = max(4, min(shortrev_len, 40)) + self.shortrev_len = max(4, min(shortrev_len, 40)) self.rlookup_uid = rlookup_uid - self._use_committer_time = use_committer_time - self._use_committer_id = use_committer_id + self.use_committer_time = use_committer_time + self.use_committer_id = use_committer_id - self.git = PyGIT.StorageFactory(path, log, not persistent_cache, - git_bin=git_bin, - git_fs_encoding=git_fs_encoding) \ - .getInstance() + try: + self.git = PyGIT.StorageFactory(path, log, not persistent_cache, + git_bin=git_bin, + git_fs_encoding=git_fs_encoding) \ + .getInstance() + except PyGIT.GitError, e: + raise TracError("%s does not appear to be a Git " + "repository." % path) Repository.__init__(self, 'git:'+path, self.params, log) @@ -406,7 +412,7 @@ class GitRepository(Repository): def short_rev(self, rev): return self.git.shortrev(self.normalize_rev(rev), - min_len=self._shortrev_len) + min_len=self.shortrev_len) def get_node(self, path, rev=None, historian=None): return GitNode(self, path, rev, self.log, None, historian) @@ -646,22 +652,37 @@ class GitChangeset(Changeset): if _children: props['children'] = _children + committer, author = self._get_committer_and_author() # use 1st author/committer as changeset owner/timestamp - if repos._use_committer_time: - _, time_ = _parse_user_time(props['committer'][0]) + c_user = a_user = c_time = a_time = None + if committer: + c_user, c_time = _parse_user_time(committer) + if author: + a_user, a_time = _parse_user_time(author) + + if repos.use_committer_time: + time = c_time or a_time else: - _, time_ = _parse_user_time(props['author'][0]) + time = a_time or c_time - if repos._use_committer_id: - user_, _ = _parse_user_time(props['committer'][0]) + if repos.use_committer_id: + user = c_user or a_user else: - user_, _ = _parse_user_time(props['author'][0]) + user = a_user or c_user # try to resolve email address to trac uid - user_ = repos.rlookup_uid(user_) or user_ + user = repos.rlookup_uid(user) or user - Changeset.__init__(self, repos, rev=sha, message=msg, author=user_, - date=time_) + Changeset.__init__(self, repos, rev=sha, message=msg, author=user, + date=time) + + def _get_committer_and_author(self): + committer = author = None + if 'committer' in self.props: + committer = self.props['committer'][0] + if 'author' in self.props: + author = self.props['author'][0] + return committer, author def get_properties(self): properties = {} @@ -672,13 +693,10 @@ class GitChangeset(Changeset): if 'children' in self.props: properties['Children'] = self.props['children'] - if 'committer' in self.props: - properties['git-committer'] = \ - _parse_user_time(self.props['committer'][0]) - - if 'author' in self.props: - properties['git-author'] = \ - _parse_user_time(self.props['author'][0]) + committer, author = self._get_committer_and_author() + if author != committer: + properties['git-committer'] = _parse_user_time(committer) + properties['git-author'] = _parse_user_time(author) branches = list(self.repos.git.get_branch_contains(self.rev, resolve=True)) Modified: incubator/bloodhound/trunk/trac/tracopt/versioncontrol/git/tests/PyGIT.py URL: http://svn.apache.org/viewvc/incubator/bloodhound/trunk/trac/tracopt/versioncontrol/git/tests/PyGIT.py?rev=1398968&r1=1398967&r2=1398968&view=diff ============================================================================== --- incubator/bloodhound/trunk/trac/tracopt/versioncontrol/git/tests/PyGIT.py (original) +++ incubator/bloodhound/trunk/trac/tracopt/versioncontrol/git/tests/PyGIT.py Tue Oct 16 20:06:09 2012 @@ -14,7 +14,7 @@ import unittest from trac.test import locate -from tracopt.versioncontrol.git.PyGIT import GitCore, Storage +from tracopt.versioncontrol.git.PyGIT import GitCore, Storage, parse_commit class GitTestCase(unittest.TestCase): @@ -32,6 +32,111 @@ class GitTestCase(unittest.TestCase): self.assertTrue(v['v_compatible']) +class TestParseCommit(unittest.TestCase): + commit2240a7b = '''\ +tree b19535236cfb6c64b798745dd3917dafc27bcd0a +parent 30aaca4582eac20a52ac7b2ec35bdb908133e5b1 +parent 5a0dc7365c240795bf190766eba7a27600be3b3e +author Linus Torvalds <torva...@linux-foundation.org> 1323915958 -0800 +committer Linus Torvalds <torva...@linux-foundation.org> 1323915958 -0800 +mergetag object 5a0dc7365c240795bf190766eba7a27600be3b3e + type commit + tag tytso-for-linus-20111214A + tagger Theodore Ts'o <ty...@mit.edu> 1323890113 -0500 + + tytso-for-linus-20111214 + -----BEGIN PGP SIGNATURE----- + Version: GnuPG v1.4.10 (GNU/Linux) + + iQIcBAABCAAGBQJO6PXBAAoJENNvdpvBGATwpuEP/2RCxmdWYZ8/6Z6pmTh3hHN5 + fx6HckTdvLQOvbQs72wzVW0JKyc25QmW2mQc5z3MjSymjf/RbEKihPUITRNbHrTD + T2sP/lWu09AKLioEg4ucAKn/A7Do3UDIkXTszvVVP/t2psVPzLeJ1njQKra14Nyz + o0+gSlnwuGx9WaxfR+7MYNs2ikdSkXIeYsiFAOY4YOxwwC99J/lZ0YaNkbI7UBtC + yu2XLIvPboa5JZXANq2G3VhVIETMmOyRTCC76OAXjqkdp9nLFWDG0ydqQh0vVZwL + xQGOmAj+l3BNTE0QmMni1w7A0SBU3N6xBA5HN6Y49RlbsMYG27aN54Fy5K2R41I3 + QXVhBL53VD6b0KaITcoz7jIGIy6qk9Wx+2WcCYtQBSIjL2YwlaJq0PL07+vRamex + sqHGDejcNY87i6AV0DP6SNuCFCi9xFYoAoMi9Wu5E9+T+Vck0okFzW/luk/FvsSP + YA5Dh+vISyBeCnWQvcnBmsUQyf8d9MaNnejZ48ath+GiiMfY8USAZ29RAG4VuRtS + 9DAyTTIBA73dKpnvEV9u4i8Lwd8hRVMOnPyOO785NwEXk3Ng08pPSSbMklW6UfCY + 4nr5UNB13ZPbXx4uoAvATMpCpYxMaLEdxmeMvgXpkekl0hHBzpVDey1Vu9fb/a5n + dQpo6WWG9HIJ23hOGAGR + =n3Lm + -----END PGP SIGNATURE----- + +Merge tag 'tytso-for-linus-20111214' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4 + +* tag 'tytso-for-linus-20111214' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4: + ext4: handle EOF correctly in ext4_bio_write_page() + ext4: remove a wrong BUG_ON in ext4_ext_convert_to_initialized + ext4: correctly handle pages w/o buffers in ext4_discard_partial_buffers() + ext4: avoid potential hang in mpage_submit_io() when blocksize < pagesize + ext4: avoid hangs in ext4_da_should_update_i_disksize() + ext4: display the correct mount option in /proc/mounts for [no]init_itable + ext4: Fix crash due to getting bogus eh_depth value on big-endian systems + ext4: fix ext4_end_io_dio() racing against fsync() + +.. using the new signed tag merge of git that now verifies the gpg +signature automatically. Yay. The branchname was just 'dev', which is +prettier. I'll tell Ted to use nicer tag names for future cases. +''' + + def test_parse(self): + msg, props = parse_commit(self.commit2240a7b) + self.assertTrue(msg) + self.assertTrue(props) + self.assertEquals( + ['30aaca4582eac20a52ac7b2ec35bdb908133e5b1', + '5a0dc7365c240795bf190766eba7a27600be3b3e'], + props['parent']) + self.assertEquals( + ['Linus Torvalds <torva...@linux-foundation.org> 1323915958 -0800'], + props['author']) + self.assertEquals(props['author'], props['committer']) + + # Merge tag + self.assertEquals(['''\ +object 5a0dc7365c240795bf190766eba7a27600be3b3e +type commit +tag tytso-for-linus-20111214A +tagger Theodore Ts\'o <ty...@mit.edu> 1323890113 -0500 + +tytso-for-linus-20111214 +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.10 (GNU/Linux) + +iQIcBAABCAAGBQJO6PXBAAoJENNvdpvBGATwpuEP/2RCxmdWYZ8/6Z6pmTh3hHN5 +fx6HckTdvLQOvbQs72wzVW0JKyc25QmW2mQc5z3MjSymjf/RbEKihPUITRNbHrTD +T2sP/lWu09AKLioEg4ucAKn/A7Do3UDIkXTszvVVP/t2psVPzLeJ1njQKra14Nyz +o0+gSlnwuGx9WaxfR+7MYNs2ikdSkXIeYsiFAOY4YOxwwC99J/lZ0YaNkbI7UBtC +yu2XLIvPboa5JZXANq2G3VhVIETMmOyRTCC76OAXjqkdp9nLFWDG0ydqQh0vVZwL +xQGOmAj+l3BNTE0QmMni1w7A0SBU3N6xBA5HN6Y49RlbsMYG27aN54Fy5K2R41I3 +QXVhBL53VD6b0KaITcoz7jIGIy6qk9Wx+2WcCYtQBSIjL2YwlaJq0PL07+vRamex +sqHGDejcNY87i6AV0DP6SNuCFCi9xFYoAoMi9Wu5E9+T+Vck0okFzW/luk/FvsSP +YA5Dh+vISyBeCnWQvcnBmsUQyf8d9MaNnejZ48ath+GiiMfY8USAZ29RAG4VuRtS +9DAyTTIBA73dKpnvEV9u4i8Lwd8hRVMOnPyOO785NwEXk3Ng08pPSSbMklW6UfCY +4nr5UNB13ZPbXx4uoAvATMpCpYxMaLEdxmeMvgXpkekl0hHBzpVDey1Vu9fb/a5n +dQpo6WWG9HIJ23hOGAGR +=n3Lm +-----END PGP SIGNATURE-----'''], props['mergetag']) + + # Message + self.assertEquals("""Merge tag 'tytso-for-linus-20111214' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4 + +* tag 'tytso-for-linus-20111214' of git://git.kernel.org/pub/scm/linux/kernel/git/tytso/ext4: + ext4: handle EOF correctly in ext4_bio_write_page() + ext4: remove a wrong BUG_ON in ext4_ext_convert_to_initialized + ext4: correctly handle pages w/o buffers in ext4_discard_partial_buffers() + ext4: avoid potential hang in mpage_submit_io() when blocksize < pagesize + ext4: avoid hangs in ext4_da_should_update_i_disksize() + ext4: display the correct mount option in /proc/mounts for [no]init_itable + ext4: Fix crash due to getting bogus eh_depth value on big-endian systems + ext4: fix ext4_end_io_dio() racing against fsync() + +.. using the new signed tag merge of git that now verifies the gpg +signature automatically. Yay. The branchname was just 'dev', which is +prettier. I'll tell Ted to use nicer tag names for future cases.""", msg) + + #class GitPerformanceTestCase(unittest.TestCase): # """Performance test. Not really a unit test. # Not self-contained: Needs a git repository and prints performance result @@ -185,6 +290,7 @@ def suite(): git = locate("git") if git: suite.addTest(unittest.makeSuite(GitTestCase, 'test')) + suite.addTest(unittest.makeSuite(TestParseCommit, 'test')) else: print("SKIP: tracopt/versioncontrol/git/tests/PyGIT.py (git cli " "binary, 'git', not found)")