Change the default inventory cache size to 1. For large projects, this reduces memory...
[jelmer/python-fastimport.git] / processor.py
1 # Copyright (C) 2008 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 """Processor of import commands.
18
19 This module provides core processing functionality including an abstract class
20 for basing real processors on. See the processors package for examples.
21 """
22
23 import sys
24 import time
25
26 from bzrlib import debug
27 from bzrlib.errors import NotBranchError
28 from bzrlib.trace import (
29     mutter,
30     note,
31     warning,
32     )
33 import errors
34
35
36 class ImportProcessor(object):
37     """Base class for import processors.
38     
39     Subclasses should override the pre_*, post_* and *_handler
40     methods as appropriate.
41     """
42
43     known_params = []
44
45     def __init__(self, bzrdir, params=None, verbose=False, outf=None):
46         if outf is None:
47             self.outf = sys.stdout
48         else:
49             self.outf = outf
50         self.verbose = verbose
51         if params is None:
52             self.params = {}
53         else:
54             self.params = params
55             self.validate_parameters()
56         self.bzrdir = bzrdir
57         if bzrdir is None:
58             # Some 'importers' don't need a repository to write to
59             self.working_tree = None
60             self.branch = None
61             self.repo = None
62         else:
63             try:
64                 # Might be inside a branch
65                 (self.working_tree, self.branch) = bzrdir._get_tree_branch()
66                 self.repo = self.branch.repository
67             except NotBranchError:
68                 # Must be inside a repository
69                 self.working_tree = None
70                 self.branch = None
71                 self.repo = bzrdir.open_repository()
72
73         # Handlers can set this to request exiting cleanly without
74         # iterating through the remaining commands
75         self.finished = False
76
77     def validate_parameters(self):
78         """Validate that the parameters are correctly specified."""
79         for p in self.params:
80             if p not in self.known_params:
81                 raise errors.UnknownParameter(p, self.known_params)
82
83     def process(self, command_iter):
84         """Import data into Bazaar by processing a stream of commands.
85
86         :param command_iter: an iterator providing commands
87         """
88         if self.working_tree is not None:
89             self.working_tree.lock_write()
90         elif self.branch is not None:
91             self.branch.lock_write()
92         elif self.repo is not None:
93             self.repo.lock_write()
94         try:
95             self._process(command_iter)
96         finally:
97             # If an unhandled exception occurred, abort the write group
98             if self.repo is not None and self.repo.is_in_write_group():
99                 self.repo.abort_write_group()
100             # Release the locks
101             if self.working_tree is not None:
102                 self.working_tree.unlock()
103             elif self.branch is not None:
104                 self.branch.unlock()
105             elif self.repo is not None:
106                 self.repo.unlock()
107
108     def _process(self, command_iter):
109         self.pre_process()
110         for cmd in command_iter():
111             try:
112                 handler = self.__class__.__dict__[cmd.name + "_handler"]
113             except KeyError:
114                 raise errors.MissingHandler(cmd.name)
115             else:
116                 self.pre_handler(cmd)
117                 handler(self, cmd)
118                 self.post_handler(cmd)
119             if self.finished:
120                 break
121         self.post_process()
122
123     def note(self, msg, *args):
124         """Output a note but timestamp it."""
125         msg = "%s %s" % (self._time_of_day(), msg)
126         note(msg, *args)
127
128     def warning(self, msg, *args):
129         """Output a warning but timestamp it."""
130         msg = "%s WARNING: %s" % (self._time_of_day(), msg)
131         warning(msg, *args)
132
133     def debug(self, mgs, *args):
134         """Output a debug message if the appropriate -D option was given."""
135         if "fast-import" in debug.debug_flags:
136             msg = "%s DEBUG: %s" % (self._time_of_day(), msg)
137             mutter(msg, *args)
138
139     def _time_of_day(self):
140         """Time of day as a string."""
141         # Note: this is a separate method so tests can patch in a fixed value
142         return time.strftime("%H:%M:%S")
143
144     def pre_process(self):
145         """Hook for logic at start of processing."""
146         pass
147
148     def post_process(self):
149         """Hook for logic at end of processing."""
150         pass
151
152     def pre_handler(self, cmd):
153         """Hook for logic before each handler starts."""
154         pass
155
156     def post_handler(self, cmd):
157         """Hook for logic after each handler finishes."""
158         pass
159
160     def progress_handler(self, cmd):
161         """Process a ProgressCommand."""
162         raise NotImplementedError(self.progress_handler)
163
164     def blob_handler(self, cmd):
165         """Process a BlobCommand."""
166         raise NotImplementedError(self.blob_handler)
167
168     def checkpoint_handler(self, cmd):
169         """Process a CheckpointCommand."""
170         raise NotImplementedError(self.checkpoint_handler)
171
172     def commit_handler(self, cmd):
173         """Process a CommitCommand."""
174         raise NotImplementedError(self.commit_handler)
175
176     def reset_handler(self, cmd):
177         """Process a ResetCommand."""
178         raise NotImplementedError(self.reset_handler)
179
180     def tag_handler(self, cmd):
181         """Process a TagCommand."""
182         raise NotImplementedError(self.tag_handler)
183
184     def feature_handler(self, cmd):
185         """Process a FeatureCommand."""
186         raise NotImplementedError(self.feature_handler)
187
188
189 class CommitHandler(object):
190     """Base class for commit handling.
191     
192     Subclasses should override the pre_*, post_* and *_handler
193     methods as appropriate.
194     """
195
196     def __init__(self, command):
197         self.command = command
198
199     def process(self):
200         self.pre_process_files()
201         for fc in self.command.file_iter():
202             try:
203                 handler = self.__class__.__dict__[fc.name[4:] + "_handler"]
204             except KeyError:
205                 raise errors.MissingHandler(fc.name)
206             else:
207                 handler(self, fc)
208         self.post_process_files()
209
210     def note(self, msg, *args):
211         """Output a note but add context."""
212         msg = "%s (%s)" % (msg, self.command.id)
213         note(msg, *args)
214
215     def warning(self, msg, *args):
216         """Output a warning but add context."""
217         msg = "WARNING: %s (%s)" % (msg, self.command.id)
218         warning(msg, *args)
219
220     def mutter(self, msg, *args):
221         """Output a mutter but add context."""
222         msg = "%s (%s)" % (msg, self.command.id)
223         mutter(msg, *args)
224
225     def debug(self, msg, *args):
226         """Output a mutter if the appropriate -D option was given."""
227         if "fast-import" in debug.debug_flags:
228             msg = "%s (%s)" % (msg, self.command.id)
229             mutter(msg, *args)
230
231     def pre_process_files(self):
232         """Prepare for committing."""
233         pass
234
235     def post_process_files(self):
236         """Save the revision."""
237         pass
238
239     def modify_handler(self, filecmd):
240         """Handle a filemodify command."""
241         raise NotImplementedError(self.modify_handler)
242
243     def delete_handler(self, filecmd):
244         """Handle a filedelete command."""
245         raise NotImplementedError(self.delete_handler)
246
247     def copy_handler(self, filecmd):
248         """Handle a filecopy command."""
249         raise NotImplementedError(self.copy_handler)
250
251     def rename_handler(self, filecmd):
252         """Handle a filerename command."""
253         raise NotImplementedError(self.rename_handler)
254
255     def deleteall_handler(self, filecmd):
256         """Handle a filedeleteall command."""
257         raise NotImplementedError(self.deleteall_handler)