/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
1
# Copyright (C) 2007 Canonical Ltd
2
#
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
#
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""The model for interacting with the git process, etc."""
18
0.200.20 by John Arbash Meinel
All tests are passing again
19
import os
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
20
import subprocess
0.200.43 by David Allouche
Ultra-experimental support for "bzr pull". No test. No sanity.
21
import tarfile
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
22
0.200.27 by David Allouche
Flat is better than nested, remove the gitlib hierarchy.
23
from bzrlib.plugins.git import errors
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
24
25
26
class GitModel(object):
27
    """API that follows GIT model closely"""
28
29
    def __init__(self, git_dir):
30
        self.git_dir = git_dir
31
32
    def git_command(self, command, args):
0.200.20 by John Arbash Meinel
All tests are passing again
33
        return ['git', command] + args
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
34
35
    def git_lines(self, command, args):
36
        cmd = self.git_command(command, args)
0.200.20 by John Arbash Meinel
All tests are passing again
37
        env = os.environ.copy()
38
        env['GIT_DIR'] = self.git_dir
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
39
        p = subprocess.Popen(cmd,
40
                             stdout=subprocess.PIPE,
0.200.20 by John Arbash Meinel
All tests are passing again
41
                             stderr=subprocess.PIPE,
42
                             env=env)
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
43
        lines = p.stdout.readlines()
44
        if p.wait() != 0:
45
            raise errors.GitCommandError(cmd, p.returncode,
46
                                         p.stderr.read().strip())
47
        return lines
48
49
    def git_line(self, command, args):
50
        lines = self.git_lines(command, args)
51
        return lines[0]
52
53
    def cat_file(self, type, object_id, pretty=False):
54
        args = []
55
        if pretty:
56
            args.append('-p')
57
        else:
58
            args.append(type)
59
        args.append(object_id)
60
        return self.git_lines('cat-file', args)
61
0.200.43 by David Allouche
Ultra-experimental support for "bzr pull". No test. No sanity.
62
    def rev_list(self, heads, max_count=None, header=False, parents=False,
63
                 topo_order=False, paths=None):
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
64
        args = []
65
        if max_count is not None:
66
            args.append('--max-count=%d' % max_count)
67
        if header:
68
            args.append('--header')
69
        if parents:
70
            args.append('--parents')
0.200.43 by David Allouche
Ultra-experimental support for "bzr pull". No test. No sanity.
71
        if topo_order:
72
            args.append('--topo-order')
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
73
        if heads is None:
74
            args.append('--all')
75
        else:
76
            args.extend(heads)
0.200.43 by David Allouche
Ultra-experimental support for "bzr pull". No test. No sanity.
77
        if paths is not None:
78
            args.append('--')
79
            args.extend(paths)
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
80
        return self.git_lines('rev-list', args)
81
82
    def rev_parse(self, git_id):
83
        args = ['--verify', git_id]
0.200.20 by John Arbash Meinel
All tests are passing again
84
        return self.git_line('rev-parse', args).strip()
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
85
86
    def get_head(self):
87
        try:
88
            return self.rev_parse('HEAD')
89
        except errors.GitCommandError, e:
90
            # Most likely, this is a null branch, so treat it as such
91
            if e.stderr == 'fatal: Needed a single revision':
92
                return None
93
            raise
94
0.200.42 by David Allouche
Rename GitModel.ancestry to .get_revision_graph.
95
    def get_revision_graph(self, revisions):
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
96
        ancestors = {}
97
        for line in self.rev_list(revisions, parents=True):
98
            entries = line.split()
99
            ancestors[entries[0]] = entries[1:]
100
        return ancestors
101
0.200.43 by David Allouche
Ultra-experimental support for "bzr pull". No test. No sanity.
102
    def get_ancestry(self, revisions):
103
        args = ['--topo-order', '--reverse'] + revisions
104
        return [line[:-1] for line in self.git_lines('rev-list', args)]
105
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
106
    def ancestor_lines(self, revisions):
107
        revision_lines = []
108
        for line in self.rev_list(revisions, header=True):
109
            if line.startswith('\x00'):
110
                yield revision_lines
111
                revision_lines = [line[1:].decode('latin-1')]
112
            else:
113
                revision_lines.append(line.decode('latin-1'))
114
        assert revision_lines == ['']
115
116
    def get_inventory(self, tree_id):
0.200.37 by David Allouche
Reimplement GitModel.get_inventory to retrieve the full inventory in a single git run.
117
        for line in self.git_lines('ls-tree', ['-r', '-t', tree_id]):
118
            # Ideally, we would use -z so we would not have to handle escaped
119
            # file names. But then we could not use readlines() to split the
120
            # data as it is read.
121
            permissions, type, hash_and_path = line.split(' ', 2)
122
            hash, name = hash_and_path.split('\t', 1)
123
            name = name[:-1] # strip trailing newline
0.200.19 by John Arbash Meinel
More refactoring. Add some direct tests for GitModel.
124
            if name.startswith('"'):
0.200.37 by David Allouche
Reimplement GitModel.get_inventory to retrieve the full inventory in a single git run.
125
                name = name[1:-1].decode('string_escape')
126
            name = name.decode('utf-8')
127
            yield permissions, type, hash, name
0.200.43 by David Allouche
Ultra-experimental support for "bzr pull". No test. No sanity.
128
129
    def get_tarpipe(self, tree_id):
130
        cmd = self.git_command('archive', [tree_id])
131
        env = os.environ.copy()
132
        env['GIT_DIR'] = self.git_dir
133
        p = subprocess.Popen(cmd,
134
                             stdin=subprocess.PIPE,
135
                             stdout=subprocess.PIPE,
136
                             stderr=subprocess.PIPE,
137
                             env=env)
138
        p.stdin.close()
139
        tarpipe = TarPipe.open(mode='r|', fileobj=p.stdout)
140
        def close_callback():
141
            if p.wait() != 0:
142
                raise errors.GitCommandError(cmd, p.returncode,
143
                                             p.stderr.read().strip())
144
        tarpipe.set_close_callback(close_callback)
145
        return tarpipe
146
147
148
class TarPipe(tarfile.TarFile):
149
150
    def set_close_callback(self, close_callback):
151
        self.__close_callback = close_callback
152
153
    def close(self):
154
        super(TarPipe, self).close()
155
        self.__close_callback()