Exemple #1
0
	def test_stats(self):
		commit = self.rorepo.commit('33ebe7acec14b25c5f84f35a664803fcab2f7781')
		stats = commit.stats
		
		def check_entries(d):
			assert isinstance(d, dict)
			for key in ("insertions", "deletions", "lines"):
				assert key in d
		# END assertion helper 
		assert stats.files 
		assert stats.total
		
		check_entries(stats.total) 
		assert "files" in stats.total
		
		for filepath, d in stats.files.items():
			check_entries(d)
		# END for each stated file
		
		# assure data is parsed properly
		michael = Actor._from_string("Michael Trier <[email protected]>")
		assert commit.author == michael
		assert commit.committer == michael
		assert commit.authored_date == 1210193388
		assert commit.committed_date == 1210193388
		assert commit.author_tz_offset == 14400, commit.author_tz_offset
		assert commit.committer_tz_offset == 14400, commit.committer_tz_offset
		assert commit.message == "initial project\n"
    def from_line(cls, line):
        """:return: New RefLogEntry instance from the given revlog line.
        :param line: line bytes without trailing newline
        :raise ValueError: If line could not be parsed"""
        line = line.decode(defenc)
        fields = line.split('\t', 1)
        if len(fields) == 1:
            info, msg = fields[0], None
        elif len(fields) == 2:
            info, msg = fields
        else:
            raise ValueError("Line must have up to two TAB-separated fields."
                             " Got %s" % repr(line))
        # END handle first split

        oldhexsha = info[:40]
        newhexsha = info[41:81]
        for hexsha in (oldhexsha, newhexsha):
            if not cls._re_hexsha_only.match(hexsha):
                raise ValueError("Invalid hexsha: %r" % (hexsha,))
            # END if hexsha re doesn't match
        # END for each hexsha

        email_end = info.find('>', 82)
        if email_end == -1:
            raise ValueError("Missing token: >")
        # END handle missing end brace

        actor = Actor._from_string(info[82:email_end + 1])
        time, tz_offset = parse_date(info[email_end + 2:])

        return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg))
Exemple #3
0
    def from_line(cls, line):
        """:return: New RefLogEntry instance from the given revlog line.
        :param line: line without trailing newline
        :raise ValueError: If line could not be parsed"""
        try:
            info, msg = line.split('\t', 2)
        except ValueError:
            raise ValueError("line is missing tab separator")
        # END handle first plit
        oldhexsha = info[:40]
        newhexsha = info[41:81]
        for hexsha in (oldhexsha, newhexsha):
            if not cls._re_hexsha_only.match(hexsha):
                raise ValueError("Invalid hexsha: %s" % hexsha)
            # END if hexsha re doesn't match
        # END for each hexsha

        email_end = info.find('>', 82)
        if email_end == -1:
            raise ValueError("Missing token: >")
        # END handle missing end brace

        actor = Actor._from_string(info[82:email_end + 1])
        time, tz_offset = parse_date(info[email_end + 2:])

        return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg))
Exemple #4
0
	def test_unicode_actor(self):
		# assure we can parse unicode actors correctly
		name = "Üäöß ÄußÉ".decode("utf-8")
		assert len(name) == 9
		special = Actor._from_string(u"%s <[email protected]>" % name)
		assert special.name == name
		assert isinstance(special.name, unicode)
Exemple #5
0
    def append_entry(cls, config_reader, filepath, oldbinsha, newbinsha, message):
        """Append a new log entry to the revlog at filepath.

        :param config_reader: configuration reader of the repository - used to obtain
            user information. May be None
        :param filepath: full path to the log file
        :param oldbinsha: binary sha of the previous commit
        :param newbinsha: binary sha of the current commit
        :param message: message describing the change to the reference
        :param write: If True, the changes will be written right away. Otherwise
            the change will not be written
        :return: RefLogEntry objects which was appended to the log
        :note: As we are append-only, concurrent access is not a problem as we 
            do not interfere with readers."""
        if len(oldbinsha) != 20 or len(newbinsha) != 20:
            raise ValueError("Shas need to be given in binary format")
        # END handle sha type
        assure_directory_exists(filepath, is_file=True)
        entry = RefLogEntry((bin_to_hex(oldbinsha), bin_to_hex(newbinsha), Actor.committer(
            config_reader), (int(time.time()), time.altzone), message))

        lf = LockFile(filepath)
        lf._obtain_lock_or_raise()

        fd = open(filepath, 'a')
        try:
            fd.write(repr(entry))
        finally:
            fd.close()
            lf._release_lock()
        # END handle write operation

        return entry
Exemple #6
0
def parse_actor_and_date(line):
    """Parse out the actor (author or committer) info from a line like::
    
        author Tom Preston-Werner <[email protected]> 1191999972 -0700
    
    :return: [Actor, int_seconds_since_epoch, int_timezone_offset]"""
    m = _re_actor_epoch.search(line)
    actor, epoch, offset = m.groups()
    return (Actor._from_string(actor), int(epoch), utctz_to_altz(offset))
    def test_from_string_should_separate_name_and_email(self):
        a = Actor._from_string("Michael Trier <[email protected]>")
        assert "Michael Trier" == a.name
        assert "[email protected]" == a.email

        # base type capabilities
        assert a == a
        assert not (a != a)
        m = set()
        m.add(a)
        m.add(a)
        assert len(m) == 1
Exemple #8
0
    def blame(self, rev, file):
        """The blame information for the given file at the given revision.

        :parm rev: revision specifier, see git-rev-parse for viable options.
        :return:
            list: [git.Commit, list: [<line>]]
            A list of tuples associating a Commit object with a list of lines that
            changed within the given commit. The Commit objects will be given in order
            of appearance."""
        data = self.git.blame(rev, '--', file, p=True, stdout_as_string=False)
        commits = dict()
        blames = list()
        info = None

        keepends = True
        for line in data.splitlines(keepends):
            try:
                line = line.rstrip().decode(defenc)
            except UnicodeDecodeError:
                firstpart = ''
                is_binary = True
            else:
                # As we don't have an idea when the binary data ends, as it could contain multiple newlines
                # in the process. So we rely on being able to decode to tell us what is is.
                # This can absolutely fail even on text files, but even if it does, we should be fine treating it
                # as binary instead
                parts = self.re_whitespace.split(line, 1)
                firstpart = parts[0]
                is_binary = False
            # end handle decode of line

            if self.re_hexsha_only.search(firstpart):
                # handles
                # 634396b2f541a9f2d58b00be1a07f0c358b999b3 1 1 7        - indicates blame-data start
                # 634396b2f541a9f2d58b00be1a07f0c358b999b3 2 2          - indicates
                # another line of blame with the same data
                digits = parts[-1].split(" ")
                if len(digits) == 3:
                    info = {'id': firstpart}
                    blames.append([None, []])
                elif info['id'] != firstpart:
                    info = {'id': firstpart}
                    blames.append([commits.get(firstpart), []])
                # END blame data initialization
            else:
                m = self.re_author_committer_start.search(firstpart)
                if m:
                    # handles:
                    # author Tom Preston-Werner
                    # author-mail <[email protected]>
                    # author-time 1192271832
                    # author-tz -0700
                    # committer Tom Preston-Werner
                    # committer-mail <[email protected]>
                    # committer-time 1192271832
                    # committer-tz -0700  - IGNORED BY US
                    role = m.group(0)
                    if firstpart.endswith('-mail'):
                        info["%s_email" % role] = parts[-1]
                    elif firstpart.endswith('-time'):
                        info["%s_date" % role] = int(parts[-1])
                    elif role == firstpart:
                        info[role] = parts[-1]
                    # END distinguish mail,time,name
                else:
                    # handle
                    # filename lib/grit.rb
                    # summary add Blob
                    # <and rest>
                    if firstpart.startswith('filename'):
                        info['filename'] = parts[-1]
                    elif firstpart.startswith('summary'):
                        info['summary'] = parts[-1]
                    elif firstpart == '':
                        if info:
                            sha = info['id']
                            c = commits.get(sha)
                            if c is None:
                                c = Commit(self, hex_to_bin(sha),
                                           author=Actor._from_string(info['author'] + ' ' + info['author_email']),
                                           authored_date=info['author_date'],
                                           committer=Actor._from_string(
                                               info['committer'] + ' ' + info['committer_email']),
                                           committed_date=info['committer_date'],
                                           message=info['summary'])
                                commits[sha] = c
                            # END if commit objects needs initial creation
                            if not is_binary:
                                if line and line[0] == '\t':
                                    line = line[1:]
                            else:
                                # NOTE: We are actually parsing lines out of binary data, which can lead to the
                                # binary being split up along the newline separator. We will append this to the blame
                                # we are currently looking at, even though it should be concatenated with the last line
                                # we have seen.
                                pass
                            # end handle line contents
                            blames[-1][0] = c
                            blames[-1][1].append(line)
                            info = {'id': sha}
                        # END if we collected commit info
                    # END distinguish filename,summary,rest
                # END distinguish author|committer vs filename,summary,rest
            # END distinguish hexsha vs other information
        return blames
Exemple #9
0
def blame(repo, start_commit, end_commit, filename):
    data = repo.git.blame('%s^..%s' % (start_commit, end_commit), '--', filename, p=True)
    commits = dict()
    blames = list()
    info = None

    for line in data.splitlines(False):
        parts = repo.re_whitespace.split(line, 1)
        firstpart = parts[0]
        if repo.re_hexsha_only.search(firstpart):
            # handles
            # 634396b2f541a9f2d58b00be1a07f0c358b999b3 1 1 7		- indicates blame-data start
            # 634396b2f541a9f2d58b00be1a07f0c358b999b3 2 2
            digits = parts[-1].split(" ")
            if len(digits) == 3:
                info = {'id': firstpart}
                blames.append([None, []])
            elif info['id'] != firstpart:
                info = {'id': firstpart}
                blames.append([commits.get(firstpart), []])
            # END blame data initialization
        else:
            m = repo.re_author_committer_start.search(firstpart)
            if m:
                # handles:
                # author Tom Preston-Werner
                # author-mail <[email protected]>
                # author-time 1192271832
                # author-tz -0700
                # committer Tom Preston-Werner
                # committer-mail <[email protected]>
                # committer-time 1192271832
                # committer-tz -0700  - IGNORED BY US
                role = m.group(0)
                if firstpart.endswith('-mail'):
                    info["%s_email" % role] = parts[-1]
                elif firstpart.endswith('-time'):
                    info["%s_date" % role] = int(parts[-1])
                elif role == firstpart:
                    info[role] = parts[-1]
                # END distinguish mail,time,name
            else:
                # handle
                # filename lib/grit.rb
                # summary add Blob
                # <and rest>
                if firstpart.startswith('filename'):
                    info['filename'] = parts[-1]
                elif firstpart.startswith('summary'):
                    info['summary'] = parts[-1]
                elif firstpart.startswith('boundary'):
                    info['boundary'] = True
                elif firstpart == '':
                    if info:
                        sha = info['id']
                        c = commits.get(sha)
                        if c is None:
                            if info.get('boundary'):
                                commits[sha] = False
                            else:
                                c = repo.CommitCls(
                                    repo,
                                    hex_to_bin(sha),
                                    author=Actor._from_string(info['author'] + ' ' + info['author_email']),
                                    authored_date=info['author_date'],
                                    committer=Actor._from_string(info['committer'] + ' ' + info['committer_email']),
                                    committed_date=info['committer_date'],
                                    message=info['summary']
                                )
                                commits[sha] = c
                        if c is not False:
                            # END if commit objects needs initial creation
                            m = repo.re_tab_full_line.search(line)
                            text,  = m.groups()
                            blames[-1][0] = c
                            blames[-1][1].append(text)
                        info = { 'id' : sha }
                    # END if we collected commit info
                # END distinguish filename,summary,rest
            # END distinguish author|committer vs filename,summary,rest
        # END distinguish hexsha vs other information

    for commit, lines in blames:
        if commit is not None:
            yield commit, lines
Exemple #10
0
    def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False, author=None, committer=None):
        """Commit the given tree, creating a commit object.

        :param repo: Repo object the commit should be part of
        :param tree: Tree object or hex or bin sha
            the tree of the new commit
        :param message: Commit message. It may be an empty string if no message is provided.
            It will be converted to a string in any case.
        :param parent_commits:
            Optional Commit objects to use as parents for the new commit.
            If empty list, the commit will have no parents at all and become
            a root commit.
            If None , the current head commit will be the parent of the
            new commit object
        :param head:
            If True, the HEAD will be advanced to the new commit automatically.
            Else the HEAD will remain pointing on the previous commit. This could
            lead to undesired results when diffing files.
        :param author: The name of the author, optional. If unset, the repository
            configuration is used to obtain this value.
        :param committer: The name of the committer, optional. If unset, the
            repository configuration is used to obtain this value.

        :return: Commit object representing the new commit

        :note:
            Additional information about the committer and Author are taken from the
            environment or from the git configuration, see git-commit-tree for
            more information"""
        if parent_commits is None:
            try:
                parent_commits = [repo.head.commit]
            except ValueError:
                # empty repositories have no head commit
                parent_commits = list()
            # END handle parent commits
        else:
            for p in parent_commits:
                if not isinstance(p, cls):
                    raise ValueError("Parent commit '%r' must be of type %s" % (p, cls))
            # end check parent commit types
        # END if parent commits are unset

        # retrieve all additional information, create a commit object, and
        # serialize it
        # Generally:
        # * Environment variables override configuration values
        # * Sensible defaults are set according to the git documentation

        # COMMITER AND AUTHOR INFO
        cr = repo.config_reader()
        env = os.environ

        committer = committer or Actor.committer(cr)
        author = author or Actor.author(cr)

        # PARSE THE DATES
        unix_time = int(time())
        offset = altzone

        author_date_str = env.get(cls.env_author_date, '')
        if author_date_str:
            author_time, author_offset = parse_date(author_date_str)
        else:
            author_time, author_offset = unix_time, offset
        # END set author time

        committer_date_str = env.get(cls.env_committer_date, '')
        if committer_date_str:
            committer_time, committer_offset = parse_date(committer_date_str)
        else:
            committer_time, committer_offset = unix_time, offset
        # END set committer time

        # assume utf8 encoding
        enc_section, enc_option = cls.conf_encoding.split('.')
        conf_encoding = cr.get_value(enc_section, enc_option, cls.default_encoding)

        # if the tree is no object, make sure we create one - otherwise
        # the created commit object is invalid
        if isinstance(tree, str):
            tree = repo.tree(tree)
        # END tree conversion

        # CREATE NEW COMMIT
        new_commit = cls(repo, cls.NULL_BIN_SHA, tree,
                         author, author_time, author_offset,
                         committer, committer_time, committer_offset,
                         message, parent_commits, conf_encoding)

        stream = BytesIO()
        new_commit._serialize(stream)
        streamlen = stream.tell()
        stream.seek(0)

        istream = repo.odb.store(IStream(cls.type, streamlen, stream))
        new_commit.binsha = istream.binsha

        if head:
            # need late import here, importing git at the very beginning throws
            # as well ...
            import git.refs
            try:
                repo.head.set_commit(new_commit, logmsg=message)
            except ValueError:
                # head is not yet set to the ref our HEAD points to
                # Happens on first commit
                master = git.refs.Head.create(repo, repo.head.ref, new_commit, logmsg="commit (initial): %s" % message)
                repo.head.set_reference(master, logmsg='commit: Switching to %s' % master)
            # END handle empty repositories
        # END advance head handling

        return new_commit
Exemple #11
0
 def test_actor(self):
     for cr in (None, self.rorepo.config_reader()):
         self.assertIsInstance(Actor.committer(cr), Actor)
         self.assertIsInstance(Actor.author(cr), Actor)
Exemple #12
0
	def blame(self, rev, file):
		"""The blame information for the given file at the given revision.

		:parm rev: revision specifier, see git-rev-parse for viable options.
		:return:
			list: [git.Commit, list: [<line>]]
			A list of tuples associating a Commit object with a list of lines that 
			changed within the given commit. The Commit objects will be given in order
			of appearance."""
		data = self.git.blame(rev, '--', file, p=True)
		commits = dict()
		blames = list()
		info = None

		for line in data.splitlines(False):
			parts = self.re_whitespace.split(line, 1)
			firstpart = parts[0]
			if self.re_hexsha_only.search(firstpart):
				# handles 
				# 634396b2f541a9f2d58b00be1a07f0c358b999b3 1 1 7		- indicates blame-data start
				# 634396b2f541a9f2d58b00be1a07f0c358b999b3 2 2
				digits = parts[-1].split(" ")
				if len(digits) == 3:
					info = {'id': firstpart}
					blames.append([None, []])
				# END blame data initialization
			else:
				m = self.re_author_committer_start.search(firstpart)
				if m:
					# handles: 
					# author Tom Preston-Werner
					# author-mail <[email protected]>
					# author-time 1192271832
					# author-tz -0700
					# committer Tom Preston-Werner
					# committer-mail <[email protected]>
					# committer-time 1192271832
					# committer-tz -0700  - IGNORED BY US
					role = m.group(0)
					if firstpart.endswith('-mail'):
						info["%s_email" % role] = parts[-1]
					elif firstpart.endswith('-time'):
						info["%s_date" % role] = int(parts[-1])
					elif role == firstpart:
						info[role] = parts[-1]
					# END distinguish mail,time,name
				else:
					# handle
					# filename lib/grit.rb
					# summary add Blob
					# <and rest>
					if firstpart.startswith('filename'):
						info['filename'] = parts[-1]
					elif firstpart.startswith('summary'):
						info['summary'] = parts[-1]
					elif firstpart == '':
						if info:
							sha = info['id']
							c = commits.get(sha)
							if c is None:
								c = Commit(	 self, hex_to_bin(sha),
											 author=Actor._from_string(info['author'] + ' ' + info['author_email']),
											 authored_date=info['author_date'],
											 committer=Actor._from_string(info['committer'] + ' ' + info['committer_email']),
											 committed_date=info['committer_date'],
											 message=info['summary'])
								commits[sha] = c
							# END if commit objects needs initial creation
							m = self.re_tab_full_line.search(line)
							text,  = m.groups()
							blames[-1][0] = c
							blames[-1][1].append( text )
							info = None
						# END if we collected commit info
					# END distinguish filename,summary,rest
				# END distinguish author|committer vs filename,summary,rest
			# END distinguish hexsha vs other information
		return blames
 def test_str_should_alias_name(self):
     a = Actor._from_string("Michael Trier <[email protected]>")
     assert a.name == str(a)
 def test_should_display_representation(self):
     a = Actor._from_string("Michael Trier <[email protected]>")
     assert '<git.Actor "Michael Trier <[email protected]>">' == repr(a)
 def test_from_string_should_handle_just_name(self):
     a = Actor._from_string("Michael Trier")
     assert "Michael Trier" == a.name
     assert None == a.email