Beispiel #1
0
class GitTestCase(TestCase):
    def setUp(self):
        self.file_history = GitFileHistory('example.txt', 'HEAD')

    def test_commits(self):
        self.assertEquals(
            [c.message for c in self.file_history.commits],
            [
                'Fifth commit', 'Fourth commit', 'Third commit',
                'Second commit', 'First commit'
            ],
            'Commit messages should be correct and ordered',
        )

    def test_navigation(self):
        commits = self.file_history.commits

        self.assertEquals(self.file_history.current_commit, commits[0])
        self.file_history.prev()
        self.assertEquals(self.file_history.current_commit, commits[1])
        self.file_history.prev()
        self.assertEquals(self.file_history.current_commit, commits[2])
        self.file_history.next()
        self.assertEquals(self.file_history.current_commit, commits[1])

    def test_navigation_beyond_end(self):
        commits = self.file_history.commits

        self.assertEquals(self.file_history.current_commit, commits[0])
        self.file_history.next()
        self.assertEquals(self.file_history.current_commit, commits[0])

    def test_navigation_before_start(self):
        commits = self.file_history.commits

        for _ in range(4):
            self.file_history.prev()

        self.assertEquals(self.file_history.current_commit, commits[4])
        self.file_history.prev()
        self.assertEquals(self.file_history.current_commit, commits[4])

    def test_blame(self):
        commits = self.file_history.commits
        for _ in range(5):
            self.file_history.prev()

        def check_blame(lines, expected_commits):
            blame = self.file_history.blame()
            for blame_line, expected_line, expected_commit in \
                zip(blame, lines, expected_commits):
                self.assertEquals(blame_line.line, expected_line)
                self.assertEquals(blame_line.sha, commits[expected_commit].sha)

        check_blame(
            ['first\n', 'second\n', 'third\n', 'fourth\n', 'fifth\n'],
            [4, 4, 4, 4, 4],
        )

        self.file_history.next()
        check_blame(
            ['first\n', 'fourth\n', 'fifth\n'],
            [4, 4, 4],
        )

        self.file_history.next()
        check_blame(
            ['another\n', 'yet another\n', 'first\n', 'fourth\n', 'fifth\n'],
            [2, 2, 4, 4, 4],
        )

        self.file_history.next()
        check_blame(
            [
                'another\n', '\n', 'yet another\n', 'first\n', 'fourth\n',
                '\n', 'fifth\n'
            ],
            [2, 1, 2, 4, 4, 1, 4],
        )

        self.file_history.next()
        check_blame(
            [
                'another\n', '\n', 'yet another\n', 'first\n', 'fourth\n',
                'fifth\n'
            ],
            [2, 1, 2, 4, 4, 4],
        )

    def test_forward_line_mappings(self):
        commits = self.file_history.commits

        self.assertEquals(
            {
                0: 0,
                1: None,
                2: None,
                3: 1,
                4: 2,
                5: 3
            },
            self.file_history.line_mapping(commits[4].sha, commits[3].sha),
        )

        self.assertEquals(
            {
                0: 2,
                1: 3,
                2: 4,
                3: 5
            },
            self.file_history.line_mapping(commits[3].sha, commits[2].sha),
        )

        self.assertEquals(
            {
                0: 0,
                1: 2,
                2: 3,
                3: 4,
                4: 6,
                5: 7
            },
            self.file_history.line_mapping(commits[2].sha, commits[1].sha),
        )

        self.assertEquals(
            {
                0: 0,
                1: 1,
                2: 2,
                3: 3,
                4: 4,
                5: None,
                6: 5,
                7: 6
            },
            self.file_history.line_mapping(commits[1].sha, commits[0].sha),
        )

    def test_reverse_line_mappings(self):
        commits = self.file_history.commits

        self.assertEquals(
            {
                0: 0,
                1: 1,
                2: 2,
                3: 3,
                4: 4,
                5: 6,
                6: 7
            },
            self.file_history.line_mapping(commits[0].sha, commits[1].sha),
        )

        self.assertEquals(
            {
                0: 0,
                1: None,
                2: 1,
                3: 2,
                4: 3,
                5: None,
                6: 4,
                7: 5
            },
            self.file_history.line_mapping(commits[1].sha, commits[2].sha),
        )

        self.assertEquals(
            {
                0: None,
                1: None,
                2: 0,
                3: 1,
                4: 2,
                5: 3
            },
            self.file_history.line_mapping(commits[2].sha, commits[3].sha),
        )

        self.assertEquals(
            {
                0: 0,
                1: 3,
                2: 4,
                3: 5
            },
            self.file_history.line_mapping(commits[3].sha, commits[4].sha),
        )
Beispiel #2
0
class GitTestCase(TestCase):
    def setUp(self):
        self.file_history = GitFileHistory('example.txt', 'HEAD')

    def test_commits(self):
        self.assertEquals(
            [c.message for c in self.file_history.commits],
            ['Fifth commit', 'Fourth commit', 'Third commit', 'Second commit',
             'First commit'],
            'Commit messages should be correct and ordered',
        )

    def test_navigation(self):
        commits = self.file_history.commits

        self.assertEquals(self.file_history.current_commit, commits[0])
        self.file_history.prev()
        self.assertEquals(self.file_history.current_commit, commits[1])
        self.file_history.prev()
        self.assertEquals(self.file_history.current_commit, commits[2])
        self.file_history.next()
        self.assertEquals(self.file_history.current_commit, commits[1])

    def test_navigation_beyond_end(self):
        commits = self.file_history.commits

        self.assertEquals(self.file_history.current_commit, commits[0])
        self.file_history.next()
        self.assertEquals(self.file_history.current_commit, commits[0])

    def test_navigation_before_start(self):
        commits = self.file_history.commits

        for _ in range(4):
            self.file_history.prev()

        self.assertEquals(self.file_history.current_commit, commits[4])
        self.file_history.prev()
        self.assertEquals(self.file_history.current_commit, commits[4])

    def test_blame(self):
        commits = self.file_history.commits
        for _ in range(5):
            self.file_history.prev()

        def check_blame(lines, expected_commits):
            blame = self.file_history.blame()
            for blame_line, expected_line, expected_commit in \
                zip(blame, lines, expected_commits):
                self.assertEquals(blame_line.line, expected_line)
                self.assertEquals(blame_line.sha, commits[expected_commit].sha)

        check_blame(
            ['first\n', 'second\n', 'third\n', 'fourth\n', 'fifth\n'],
            [4, 4, 4, 4, 4],
        )

        self.file_history.next()
        check_blame(
            ['first\n', 'fourth\n', 'fifth\n'],
            [4, 4, 4],
        )

        self.file_history.next()
        check_blame(
            ['another\n', 'yet another\n', 'first\n', 'fourth\n', 'fifth\n'],
            [2, 2, 4, 4, 4],
        )

        self.file_history.next()
        check_blame(
            ['another\n', '\n', 'yet another\n', 'first\n', 'fourth\n', '\n',
             'fifth\n'],
            [2, 1, 2, 4, 4, 1, 4],
        )

        self.file_history.next()
        check_blame(
            ['another\n', '\n', 'yet another\n', 'first\n', 'fourth\n',
             'fifth\n'],
            [2, 1, 2, 4, 4, 4],
        )

    def test_forward_line_mappings(self):
        commits = self.file_history.commits

        self.assertEquals(
            {0:0, 1:None, 2:None, 3:1, 4:2, 5:3},
            self.file_history.line_mapping(commits[4].sha, commits[3].sha),
        )

        self.assertEquals(
            {0:2, 1:3, 2:4, 3:5},
            self.file_history.line_mapping(commits[3].sha, commits[2].sha),
        )

        self.assertEquals(
            {0:0, 1:2, 2:3, 3:4, 4:6, 5:7},
            self.file_history.line_mapping(commits[2].sha, commits[1].sha),
        )

        self.assertEquals(
            {0:0, 1:1, 2:2, 3:3, 4:4, 5:None, 6:5, 7:6},
            self.file_history.line_mapping(commits[1].sha, commits[0].sha),
        )

    def test_reverse_line_mappings(self):
        commits = self.file_history.commits

        self.assertEquals(
            {0:0, 1:1, 2:2, 3:3, 4:4, 5:6, 6:7},
            self.file_history.line_mapping(commits[0].sha, commits[1].sha),
        )

        self.assertEquals(
            {0:0, 1:None, 2:1, 3:2, 4:3, 5:None, 6:4, 7:5},
            self.file_history.line_mapping(commits[1].sha, commits[2].sha),
        )

        self.assertEquals(
            {0:None, 1:None, 2:0, 3:1, 4:2, 5:3},
            self.file_history.line_mapping(commits[2].sha, commits[3].sha),
        )

        self.assertEquals(
            {0:0, 1:3, 2:4, 3:5},
            self.file_history.line_mapping(commits[3].sha, commits[4].sha),
        )
Beispiel #3
0
class GitBrowser(ModalScrollingInterface):
    """
    Provides the user interface for the git browse tool.
    """

    exit_keys = ModalScrollingInterface.exit_keys + (ord('s'), )
    modes = {
        '/': 'search',
        '?': 'reverse_search',
    }

    def __init__(self, path, commit):
        super(GitBrowser, self).__init__()
        self.file_history = GitFileHistory(path, commit)
        self.search_term = None
        self.reverse_search = False

    def content(self):
        return self.file_history.blame()

    def draw_content_line(self, line, row, window, highlight):
        if highlight:
            commit_color = self.INV_YELLOW
            code_color = self.INV_GREEN if line.current else self.INV_WHITE
            search_result_color = 0
        else:
            commit_color = self.YELLOW
            code_color = self.GREEN if line.current else 0
            search_result_color = self.INV_WHITE

        window.addstr(row, 0, line.sha[:7], commit_color)
        window.addstr(row, 7, '+ ' if line.current else '  ', code_color)

        cols = curses.COLS - 9
        padded_line = line.line[:cols].rstrip().ljust(cols, ' ')
        window.addstr(row, 9, padded_line, code_color)

        if self.search_term:
            search_start = 0
            try:
                while True:
                    index = line.line.index(self.search_term, search_start)
                    search_start = index + len(self.search_term)
                    window.addstr(row, 9 + index, self.search_term,
                                  search_result_color)
            except ValueError:
                pass

    def finalise(self, exit_key):
        if exit_key == ord('s'):
            current_sha = self.file_history.current_commit.sha
            os.execvp('git', ('git', 'show', current_sha))

    def handle_input(self, mode, data):
        if mode == 'search' or mode == 'reverse_search':
            self.search_term = data
            self.reverse_search = (mode == 'reverse_search')
            self.next_search_match()
            self._draw()

    def get_status(self):
        return '%(path)s @ %(sha)s by %(author)s: %(message)s' % {
            'path': self.file_history.path,
            'sha': self.file_history.current_commit.sha[:7],
            'author': self.file_history.current_commit.author,
            'message': self.file_history.current_commit.message,
        }

    def _move_commit(self, method_name):
        start = self.file_history.current_commit.sha

        method = getattr(self.file_history, method_name)
        if not method():
            curses.beep()
            return

        finish = self.file_history.current_commit.sha

        mapping = self.file_history.line_mapping(start, finish)
        new_highlight_line = mapping.get(self.highlight_line)
        if new_highlight_line is not None:
            self.highlight_line = new_highlight_line
        else:
            # The highlight_line setter validates the value, so it makes
            # sense to set it to the same value here to make sure that it's
            # not out of range for the newly loaded revision of the file.
            self.highlight_line = self.highlight_line

    @ModalScrollingInterface.key_bindings(']')
    def next_commit(self, times=1):
        for i in range(0, times):
            self._move_commit('next')

    @ModalScrollingInterface.key_bindings('[')
    def prev_commit(self, times=1):
        for i in range(0, times):
            self._move_commit('prev')

    def _next_search_match(self, times=1):
        if not self.search_term:
            curses.beep()
            return

        moved = False
        for n in range(0, times):
            possible_matches = self.content()[self.highlight_line + 1:]
            for i, line in enumerate(possible_matches):
                if self.search_term in line.line:
                    self.highlight_line += i + 1
                    moved = True
                    break

        if not moved:
            curses.beep()

    def _prev_search_match(self, times=1):
        if not self.search_term:
            curses.beep()
            return

        moved = False
        for n in range(0, times):
            possible_matches = self.content()[:self.highlight_line]
            for i, line in enumerate(reversed(possible_matches)):
                if self.search_term in line.line:
                    self.highlight_line -= i + 1
                    moved = True
                    break

        if not moved:
            curses.beep()

    @ModalScrollingInterface.key_bindings('n')
    def next_search_match(self, times=1):
        if self.reverse_search:
            self._prev_search_match(times)
        else:
            self._next_search_match(times)

    @ModalScrollingInterface.key_bindings('N')
    def prev_search_match(self, times=1):
        if self.reverse_search:
            self._next_search_match(times)
        else:
            self._prev_search_match(times)
Beispiel #4
0
class GitBrowser(ModalScrollingInterface):
    """
    Provides the user interface for the git browse tool.
    """

    exit_keys = ModalScrollingInterface.exit_keys + (ord('s'), )
    modes = {
        '/': 'search',
        '?': 'reverse_search',
    }

    def __init__(self, path, commit):
        super(GitBrowser, self).__init__()
        self.file_history = GitFileHistory(path, commit)
        self.search_term = None
        self.reverse_search = False

    def content(self):
        return self.file_history.blame()

    def draw_content_line(self, line, row, window, highlight):
        if highlight:
            commit_color = self.INV_YELLOW
            code_color = self.INV_GREEN if line.current else self.INV_WHITE
            search_result_color = 0
        else:
            commit_color = self.YELLOW
            code_color = self.GREEN if line.current else 0
            search_result_color = self.INV_WHITE

        window.addstr(row, 0, line.sha[:7], commit_color)
        window.addstr(row, 7, '+ ' if line.current else '  ', code_color)

        cols = curses.COLS - 9
        padded_line = line.line[:cols].rstrip().ljust(cols, ' ')
        window.addstr(row, 9, padded_line, code_color)

        if self.search_term:
            search_start = 0
            try:
                while True:
                    index = line.line.index(self.search_term, search_start)
                    search_start = index + len(self.search_term)
                    window.addstr(row, 9+index, self.search_term,
                                  search_result_color)
            except ValueError:
                pass

    def finalise(self, exit_key):
        if exit_key == ord('s'):
            current_sha = self.file_history.current_commit.sha
            os.execvp('git', ('git', 'show', current_sha))

    def handle_input(self, mode, data):
        if mode == 'search' or mode == 'reverse_search':
            self.search_term = data
            self.reverse_search = (mode == 'reverse_search')
            self.next_search_match()
            self._draw()

    def get_status(self):
        return '%(path)s @ %(sha)s by %(author)s: %(message)s' % {
            'path': self.file_history.path,
            'sha': self.file_history.current_commit.sha[:7],
            'author': self.file_history.current_commit.author,
            'message': self.file_history.current_commit.message,
        }

    def _move_commit(self, method_name):
        start = self.file_history.current_commit.sha

        method = getattr(self.file_history, method_name)
        if not method():
            curses.beep()
            return

        finish = self.file_history.current_commit.sha

        mapping = self.file_history.line_mapping(start, finish)
        new_highlight_line = mapping.get(self.highlight_line)
        if new_highlight_line is not None:
            self.highlight_line = new_highlight_line
        else:
            # The highlight_line setter validates the value, so it makes
            # sense to set it to the same value here to make sure that it's
            # not out of range for the newly loaded revision of the file.
            self.highlight_line = self.highlight_line

    @ModalScrollingInterface.key_bindings(']')
    def next_commit(self, times=1):
        for i in range(0,times):
            self._move_commit('next')

    @ModalScrollingInterface.key_bindings('[')
    def prev_commit(self, times=1):
        for i in range(0,times):
            self._move_commit('prev')

    def _next_search_match(self, times=1):
        if not self.search_term:
            curses.beep()
            return

        moved = False
        for n in range(0,times):
            possible_matches = self.content()[self.highlight_line + 1:]
            for i, line in enumerate(possible_matches):
                if self.search_term in line.line:
                    self.highlight_line += i + 1
                    moved = True
                    break

        if not moved:
            curses.beep()

    def _prev_search_match(self, times=1):
        if not self.search_term:
            curses.beep()
            return

        moved = False
        for n in range(0,times):
            possible_matches = self.content()[:self.highlight_line]
            for i, line in enumerate(reversed(possible_matches)):
                if self.search_term in line.line:
                    self.highlight_line -= i + 1
                    moved = True
                    break

        if not moved:
            curses.beep()

    @ModalScrollingInterface.key_bindings('n')
    def next_search_match(self, times=1):
        if self.reverse_search:
            self._prev_search_match(times)
        else:
            self._next_search_match(times)

    @ModalScrollingInterface.key_bindings('N')
    def prev_search_match(self, times=1):
        if self.reverse_search:
            self._next_search_match(times)
        else:
            self._prev_search_match(times)