git-remote-hg: improve sanitation of local repo urls
[git] / git_remote_helpers / fastimport / 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 import logging
26
27 from git_remote_helpers.fastimport import errors
28
29 log = logging.getLogger(__name__)
30
31
32 class ImportProcessor(object):
33     """Base class for import processors.
34     
35     Subclasses should override the pre_*, post_* and *_handler
36     methods as appropriate.
37     """
38
39     known_params = []
40
41     def __init__(self, params=None, verbose=False, outf=None):
42         if outf is None:
43             self.outf = sys.stdout
44         else:
45             self.outf = outf
46         self.verbose = verbose
47         if params is None:
48             self.params = {}
49         else:
50             self.params = params
51             self.validate_parameters()
52
53         # Handlers can set this to request exiting cleanly without
54         # iterating through the remaining commands
55         self.finished = False
56
57     def validate_parameters(self):
58         """Validate that the parameters are correctly specified."""
59         for p in self.params:
60             if p not in self.known_params:
61                 raise errors.UnknownParameter(p, self.known_params)
62
63     def process(self, commands):
64         """Process a stream of fast-import commands from a parser.
65
66         :param commands: a sequence of commands.ImportCommand objects
67         """
68         self.pre_process()
69         for cmd in commands:
70             try:
71                 handler = self.__class__.__dict__[cmd.name + "_handler"]
72             except KeyError:
73                 raise errors.MissingHandler(cmd.name)
74             else:
75                 self.pre_handler(cmd)
76                 handler(self, cmd)
77                 self.post_handler(cmd)
78             if self.finished:
79                 break
80         self.post_process()
81
82     def pre_process(self):
83         """Hook for logic at start of processing.
84
85         Called just before process() starts iterating over its sequence
86         of commands.
87         """
88         pass
89
90     def post_process(self):
91         """Hook for logic at end of successful processing.
92
93         Called after process() finishes successfully iterating over its
94         sequence of commands (i.e. not called if an exception is raised
95         while processing commands).
96         """
97         pass
98
99     def pre_handler(self, cmd):
100         """Hook for logic before each handler starts."""
101         pass
102
103     def post_handler(self, cmd):
104         """Hook for logic after each handler finishes."""
105         pass
106
107     def progress_handler(self, cmd):
108         """Process a ProgressCommand."""
109         raise NotImplementedError(self.progress_handler)
110
111     def blob_handler(self, cmd):
112         """Process a BlobCommand."""
113         raise NotImplementedError(self.blob_handler)
114
115     def checkpoint_handler(self, cmd):
116         """Process a CheckpointCommand."""
117         raise NotImplementedError(self.checkpoint_handler)
118
119     def commit_handler(self, cmd):
120         """Process a CommitCommand."""
121         raise NotImplementedError(self.commit_handler)
122
123     def reset_handler(self, cmd):
124         """Process a ResetCommand."""
125         raise NotImplementedError(self.reset_handler)
126
127     def tag_handler(self, cmd):
128         """Process a TagCommand."""
129         raise NotImplementedError(self.tag_handler)
130
131     def feature_handler(self, cmd):
132         """Process a FeatureCommand."""
133         raise NotImplementedError(self.feature_handler)
134
135
136 class CommitHandler(object):
137     """Base class for commit handling.
138     
139     Subclasses should override the pre_*, post_* and *_handler
140     methods as appropriate.
141     """
142
143     def __init__(self, command):
144         self.command = command
145
146     def process(self):
147         self.pre_process_files()
148         for fc in self.command.file_cmds:
149             try:
150                 handler = self.__class__.__dict__[fc.name[4:] + "_handler"]
151             except KeyError:
152                 raise errors.MissingHandler(fc.name)
153             else:
154                 handler(self, fc)
155         self.post_process_files()
156
157     def _log(self, level, msg, *args):
158         log.log(level, msg + " (%s)", *(args + (self.command.id,)))
159
160     # Logging methods: unused in this library, but used by
161     # bzr-fastimport.  Could be useful for other subclasses.
162
163     def note(self, msg, *args):
164         """log.info() with context about the command"""
165         self._log(logging.INFO, msg, *args)
166
167     def warning(self, msg, *args):
168         """log.warning() with context about the command"""
169         self._log(logging.WARNING, msg, *args)
170
171     def debug(self, msg, *args):
172         """log.debug() with context about the command"""
173         self._log(logging.DEBUG, msg, *args)
174
175     def pre_process_files(self):
176         """Prepare for committing."""
177         pass
178
179     def post_process_files(self):
180         """Save the revision."""
181         pass
182
183     def modify_handler(self, filecmd):
184         """Handle a filemodify command."""
185         raise NotImplementedError(self.modify_handler)
186
187     def delete_handler(self, filecmd):
188         """Handle a filedelete command."""
189         raise NotImplementedError(self.delete_handler)
190
191     def copy_handler(self, filecmd):
192         """Handle a filecopy command."""
193         raise NotImplementedError(self.copy_handler)
194
195     def rename_handler(self, filecmd):
196         """Handle a filerename command."""
197         raise NotImplementedError(self.rename_handler)
198
199     def deleteall_handler(self, filecmd):
200         """Handle a filedeleteall command."""
201         raise NotImplementedError(self.deleteall_handler)
202
203
204 def parseMany(filenames, parser_factory, processor):
205     """Parse multiple input files, sending the results all to
206     'processor'.  parser_factory must be a callable that takes one input
207     file and returns an ImportParser instance, e.g. the ImportParser
208     class object itself.  Each file in 'filenames' is opened, parsed,
209     and closed in turn.  For filename \"-\", reads stdin.
210     """
211     for filename in filenames:
212         if filename == "-":
213             infile = sys.stdin
214         else:
215             infile = open(filename, "rb")
216
217         try:
218             parser = parser_factory(infile)
219             processor.process(parser.parse())
220         finally:
221             if filename != "-":
222                 infile.close()