Mercurial > hgsubversion
annotate tests/fixtures/rsvn.py @ 1229:46523cdfd3b0 stable 1.6.3
pushmod: prepend "link " to base text for links
http://svn.apache.org/viewvc?view=revision&revision=1223036 exposes
what is arguably a bug in hgsubversion push code. Specifically, when
we are receiving text from the server in an editor, we prepend a "link
" to the text of symlinks when opening a file and strip it when
closing a file. We don't, however, prepend "link " to the base we use
when sending text changes to the server.
This was working before because prior to that revision, the first
thing subversion did was to check whether the entirety of the before
text or the entirety of the after text was less than 64 bytes. In
that case, it just sent the entirety of the after text as a single
insert operation. I'd expect most, but not all symlinks to fit under
the 64 byte limit, including the leading "link " text on the
subversion end.
After the change, the first thing subversion does is check for a
leading match that is more than 4 bytes long, or that is the full
length of the after text. In this case, it sends a copy operation for
the leading match, and then goes into the if < 64 bytes remaining send
the whole thing behavior. It also looks for trailing matches of more
than 4 bytes even in the <64 byte case, but that's not what breaks the
tests.
Incidentally, changing the destination of long symlinks was broken
even before this subversion change. This diff includes test additions
that cover that breakage.
author | David Schleimer <dschleimer@gmail.com> |
---|---|
date | Thu, 07 Aug 2014 19:30:26 -0700 |
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() |