Mercurial > hgsubversion
annotate tests/fixtures/rsvn.py @ 937:fb6f6b7fa5a5
editor: implement file batons
The concept of current.file is incorrect, svn_delta.h documents open
file lifetime as:
* 5. When the producer calls @c open_file or @c add_file, either:
*
* (a) The producer must follow with any changes to the file
* (@c change_file_prop and/or @c apply_textdelta, as applicable),
* followed by a @c close_file call, before issuing any other file
* or directory calls, or
*
* (b) The producer must follow with a @c change_file_prop call if
* it is applicable, before issuing any other file or directory
* calls; later, after all directory batons including the root
* have been closed, the producer must issue @c apply_textdelta
* and @c close_file calls.
So, an open file can be kept open until after the root directory is
closed and have deltas applied afterwards. In the meantime, other files
may have been opened and patched, overwriting the current.file variable.
This patch fixes it by introducing file batons bound to file paths, and
using them to deduce the correct target in apply_textdelta(). In theory,
open files could be put in a staging area until they are closed and
moved in the RevisionData. But the current code registers files copied
during a directory copy as open files and these will not receive a
close_file() event. This separation will be enforced later.
author | Patrick Mezard <patrick@mezard.eu> |
---|---|
date | Sun, 23 Sep 2012 19:52:48 +0200 |
parents | e9af7eba88db |
children |
rev | line source |
---|---|
581 | 1 #!/usr/bin/env python2 |
2 | |
3 # LICENSE | |
4 # | |
5 # Copyright (c) 2004, Francois Beausoleil | |
6 # All rights reserved. | |
7 # | |
8 # Redistribution and use in source and binary forms, with or without | |
9 # modification, are permitted provided that the following conditions | |
10 # are met: | |
11 # | |
12 # * Redistributions of source code must retain the above copyright | |
13 # notice, this list of conditions and the following disclaimer. | |
14 # * Redistributions in binary form must reproduce the above copyright | |
15 # notice, this list of conditions and the following disclaimer in | |
16 # the documentation and/or other materials provided with the | |
17 # distribution. | |
18 # * Neither the name of the Francois Beausoleil nor the names of its | |
19 # contributors may be used to endorse or promote products derived | |
20 # from this software without specific prior written permission. | |
21 # | |
22 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
23 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
24 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
25 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
26 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
27 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
28 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
29 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
30 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
31 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
32 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
33 | |
34 import getopt | |
35 import sys | |
36 import os | |
37 import re | |
38 import traceback | |
39 from svn import core, repos, fs | |
40 | |
41 VERSION='$Id: rsvn.py 20 2005-09-23 15:08:08Z fbos $' | |
42 RCOPY_RE = re.compile('^\s*rcopy\s+(.+)\s+(.+)$') | |
43 RMOVE_RE = re.compile('^\s*rmove\s+(.+)\s+(.+)$') | |
44 RMKDIR_RE = re.compile('^\s*rmkdir\s+(.+)$') | |
45 RDELETE_RE = re.compile('^\s*rdelete\s+(.+)$') | |
46 COMMENT_RE = re.compile('(?:^\s*#)|(?:^\s*$)') | |
47 | |
48 def usage(error=None): | |
49 if error: | |
50 print 'Error: %s\n' % error | |
51 print 'USAGE: %s --message=MESSAGE repos_path [--username=USERNAME]' % ( | |
52 os.path.basename(sys.argv[0])) | |
53 print '' | |
54 print ' --help, -h print this usage message and exit with success' | |
55 print ' --version print the version number' | |
56 print ' --username=USERNAME Username to execute the commands under' | |
57 print ' --message=LOG_MSG Log message to execute the commit with' | |
58 print '' | |
59 print 'Reads STDIN and parses the following commands, to execute them on the server, ' | |
60 print 'all within the same transaction:' | |
61 print '' | |
62 print ' rcopy SRC DEST Copy the HEAD revision of a file or folder' | |
63 print ' rmove SRC DEST Copy + delete the HEAD revision of a file or folder' | |
64 print ' rdelete TARGET Deletes something from the repository' | |
65 print ' rmkdir TARGET Creates a new folder (must create parents first)' | |
66 print ' # Initiates a comment' | |
67 print '' | |
68 # 12345678901234567890123456789012345678901234567890123456789012345678901234567890 | |
69 | |
70 | |
71 class Transaction: | |
72 """Represents a transaction in a Subversion repository | |
73 | |
74 Transactions are long-lived objects which exist in the repository, | |
75 and are used to build an intermediate representation of a new | |
76 revision. Once the transaction is committed, the repository | |
77 bumps the revision number, and links the new transaction in the | |
78 Subversion filesystem.""" | |
79 | |
80 def __init__(self, repository, rev, username, message, pool, logger=None): | |
81 if logger: | |
82 self.logger = logger | |
83 else: | |
84 self.logger = sys.stdout | |
85 self.pool = pool | |
86 self.rev = rev | |
87 | |
88 self.fsptr = repos.svn_repos_fs(repository) | |
89 self.rev_root = fs.revision_root(self.fsptr, self.rev, | |
90 self.pool) | |
91 self.txnp = repos.svn_repos_fs_begin_txn_for_commit( | |
92 repository, self.rev, username, message, self.pool) | |
93 self.txn_root = fs.txn_root(self.txnp, self.pool) | |
94 self.log('Base revision %d\n' % rev) | |
95 | |
96 def commit(self): | |
97 values = fs.commit_txn(self.txnp, self.pool) | |
98 return values[1] | |
99 | |
100 def rollback(self): | |
101 fs.abort_txn(self.txnp, self.pool) | |
102 | |
103 def copy(self, src, dest, subpool): | |
104 self.log('A + %s\n' % dest) | |
105 fs.copy(self.rev_root, src, self.txn_root, dest, subpool) | |
106 | |
107 def delete(self, entry, subpool): | |
108 self.log('D %s\n' % entry) | |
109 fs.delete(self.txn_root, entry, subpool) | |
110 | |
111 def mkdir(self, entry, subpool): | |
112 self.log('A %s\n' % entry) | |
113 fs.make_dir(self.txn_root, entry, subpool) | |
114 | |
115 def move(self, src, dest, subpool): | |
116 self.copy(src, dest, subpool) | |
117 self.delete(src, subpool) | |
118 | |
119 def log(self, msg): | |
120 self.logger.write(msg) | |
121 | |
122 | |
123 class Repository: | |
124 """Represents a Subversion repository, and allows common operations | |
125 on it.""" | |
126 | |
127 def __init__(self, repos_path, pool, logger=None): | |
128 if logger: | |
129 self.logger = logger | |
130 else: | |
131 self.logger = sys.stdout | |
132 self.pool = pool | |
133 assert self.pool | |
134 | |
135 self.repo = repos.svn_repos_open(repos_path, self.pool) | |
136 self.fsptr = repos.svn_repos_fs(self.repo) | |
137 | |
138 def get_youngest(self): | |
139 """Returns the youngest revision in the repository.""" | |
140 return fs.youngest_rev(self.fsptr, self.pool) | |
141 | |
142 def begin(self, username, log_msg): | |
143 """Initiate a new Transaction""" | |
144 return Transaction(self.repo, self.get_youngest(), username, | |
145 log_msg, self.pool, self.logger) | |
146 | |
147 def close(self): | |
148 """Close the repository, aborting any uncommitted transactions""" | |
149 core.svn_pool_destroy(self.pool) | |
150 core.apr_terminate() | |
151 | |
152 def subpool(self): | |
153 """Instantiates a new pool from the master pool""" | |
154 return core.svn_pool_create(self.pool) | |
155 | |
156 def delete_pool(self, pool): | |
157 """Deletes the passed-in pool. Returns None, to assign to pool in | |
158 caller.""" | |
159 core.svn_pool_destroy(pool) | |
160 return None | |
161 | |
162 def rsvn(pool): | |
163 log_msg = None | |
164 | |
165 try: | |
166 opts, args = getopt.getopt(sys.argv[1:], 'vh', | |
167 ["help", "username=", "message=", "version"]) | |
168 except getopt.GetoptError, e: | |
169 sys.stderr.write(str(e) + '\n\n') | |
170 usage() | |
171 sys.exit(1) | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
172 |
581 | 173 for opt, value in opts: |
174 if opt == '--version': | |
175 print '%s version %s' % (os.path.basename(sys.argv[0]), VERSION) | |
176 sys.exit(0) | |
177 elif opt == '--help' or opt == '-h': | |
178 usage() | |
179 sys.exit(0) | |
180 elif opt == '--username': | |
181 username = value | |
182 elif opt == '--message': | |
183 log_msg = value | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
184 |
581 | 185 if log_msg == None: |
186 usage('Missing --message argument') | |
187 sys.exit(1) | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
188 |
581 | 189 if len(args) != 1: |
190 usage('Missing repository path argument') | |
191 sys.exit(1) | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
192 |
581 | 193 repos_path = args[0] |
194 print 'Accessing repository at [%s]' % repos_path | |
195 | |
196 repository = Repository(repos_path, pool) | |
197 sub = repository.subpool() | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
198 |
581 | 199 try: |
200 txn = repository.begin(username, log_msg) | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
201 |
581 | 202 # Read commands from STDIN |
203 lineno = 0 | |
204 for line in sys.stdin: | |
205 lineno += 1 | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
206 |
581 | 207 core.svn_pool_clear(sub) |
208 try: | |
209 if COMMENT_RE.search(line): | |
210 continue | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
211 |
581 | 212 match = RCOPY_RE.search(line) |
213 if match: | |
214 src = match.group(1) | |
215 dest = match.group(2) | |
216 txn.copy(src, dest, sub) | |
217 continue | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
218 |
581 | 219 match = RMOVE_RE.search(line) |
220 if match: | |
221 src = match.group(1) | |
222 dest = match.group(2) | |
223 txn.move(src, dest, sub) | |
224 continue | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
225 |
581 | 226 match = RMKDIR_RE.search(line) |
227 if match: | |
228 entry = match.group(1) | |
229 txn.mkdir(entry, sub) | |
230 continue | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
231 |
581 | 232 match = RDELETE_RE.search(line) |
233 if match: | |
234 entry = match.group(1) | |
235 txn.delete(entry, sub) | |
236 continue | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
237 |
581 | 238 raise NameError, ('Unknown command [%s] on line %d' % |
239 (line, lineno)) | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
240 |
581 | 241 except: |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
242 sys.stderr.write(('Exception occured while processing line %d:\n' % |
581 | 243 lineno)) |
244 etype, value, tb = sys.exc_info() | |
245 traceback.print_exception(etype, value, tb, None, sys.stderr) | |
246 sys.stderr.write('\n') | |
247 txn.rollback() | |
248 sys.exit(1) | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
249 |
581 | 250 new_rev = txn.commit() |
251 print '\nCommitted revision %d.' % new_rev | |
832
e9af7eba88db
globally: clean up whitespace around operators and commas to conform with PEP8
Yonggang Luo <luoyonggang@gmail.com>
parents:
581
diff
changeset
|
252 |
581 | 253 finally: |
254 print '\nRepository closed.' | |
255 | |
256 def main(): | |
257 core.run_app(rsvn) | |
258 | |
259 if __name__ == '__main__': | |
260 main() |