Mercurial > hgsubversion
comparison cmdutil.py @ 242:06130689a2c8
Move push into svncommands.
| author | Dirkjan Ochtman <dirkjan@ochtman.nl> |
|---|---|
| date | Wed, 08 Apr 2009 17:53:48 +0200 |
| parents | 4950b18cf949 |
| children | 2027f851d60c |
comparison
equal
deleted
inserted
replaced
| 241:4950b18cf949 | 242:06130689a2c8 |
|---|---|
| 1 | |
| 2 from mercurial import util as hgutil | |
| 3 | |
| 4 from svn import core | |
| 5 | |
| 6 import svnwrap | |
| 7 import svnexternals | |
| 8 | |
| 9 | |
| 10 class BaseException(Exception): | |
| 11 pass | |
| 12 | |
| 13 | |
| 14 class NoFilesException(BaseException): | |
| 15 """Exception raised when you try and commit without files. | |
| 16 """ | |
| 17 | |
| 1 | 18 |
| 2 def replay_convert_rev(hg_editor, svn, r): | 19 def replay_convert_rev(hg_editor, svn, r): |
| 3 hg_editor.set_current_rev(r) | 20 hg_editor.set_current_rev(r) |
| 4 svn.get_replay(r.revnum, hg_editor) | 21 svn.get_replay(r.revnum, hg_editor) |
| 5 i = 1 | 22 i = 1 |
| 28 data, mode = svn.get_file(p, r.revnum) | 45 data, mode = svn.get_file(p, r.revnum) |
| 29 hg_editor.set_file(p, data, 'x' in mode, 'l' in mode) | 46 hg_editor.set_file(p, data, 'x' in mode, 'l' in mode) |
| 30 hg_editor.missing_plaintexts = set() | 47 hg_editor.missing_plaintexts = set() |
| 31 hg_editor.ui.note('\n') | 48 hg_editor.ui.note('\n') |
| 32 hg_editor.commit_current_delta() | 49 hg_editor.commit_current_delta() |
| 50 | |
| 51 | |
| 52 def _isdir(svn, branchpath, svndir): | |
| 53 try: | |
| 54 svn.list_dir('%s/%s' % (branchpath, svndir)) | |
| 55 return True | |
| 56 except core.SubversionException: | |
| 57 return False | |
| 58 | |
| 59 | |
| 60 def _getdirchanges(svn, branchpath, parentctx, ctx, changedfiles, extchanges): | |
| 61 """Compute directories to add or delete when moving from parentctx | |
| 62 to ctx, assuming only 'changedfiles' files changed, and 'extchanges' | |
| 63 external references changed (as returned by svnexternals.diff()). | |
| 64 | |
| 65 Return (added, deleted) where 'added' is the list of all added | |
| 66 directories and 'deleted' the list of deleted directories. | |
| 67 Intermediate directories are included: if a/b/c is new and requires | |
| 68 the addition of a/b and a, those will be listed too. Intermediate | |
| 69 deleted directories are also listed, but item order of undefined | |
| 70 in either list. | |
| 71 """ | |
| 72 def finddirs(path, includeself=False): | |
| 73 if includeself: | |
| 74 yield path | |
| 75 pos = path.rfind('/') | |
| 76 while pos != -1: | |
| 77 yield path[:pos] | |
| 78 pos = path.rfind('/', 0, pos) | |
| 79 | |
| 80 def getctxdirs(ctx, keptdirs, extdirs): | |
| 81 dirs = {} | |
| 82 for f in ctx.manifest(): | |
| 83 for d in finddirs(f): | |
| 84 if d in dirs: | |
| 85 break | |
| 86 if d in keptdirs: | |
| 87 dirs[d] = 1 | |
| 88 for extdir in extdirs: | |
| 89 for d in finddirs(extdir, True): | |
| 90 dirs[d] = 1 | |
| 91 return dirs | |
| 92 | |
| 93 deleted, added = [], [] | |
| 94 changeddirs = {} | |
| 95 for f in changedfiles: | |
| 96 if f in parentctx and f in ctx: | |
| 97 # Updated files cannot cause directories to be created | |
| 98 # or removed. | |
| 99 continue | |
| 100 for d in finddirs(f): | |
| 101 changeddirs[d] = 1 | |
| 102 for e in extchanges: | |
| 103 if not e[1] or not e[2]: | |
| 104 for d in finddirs(e[0], True): | |
| 105 changeddirs[d] = 1 | |
| 106 if not changeddirs: | |
| 107 return added, deleted | |
| 108 olddirs = getctxdirs(parentctx, changeddirs, | |
| 109 [e[0] for e in extchanges if e[1]]) | |
| 110 newdirs = getctxdirs(ctx, changeddirs, | |
| 111 [e[0] for e in extchanges if e[2]]) | |
| 112 | |
| 113 for d in newdirs: | |
| 114 if d not in olddirs and not _isdir(svn, branchpath, d): | |
| 115 added.append(d) | |
| 116 | |
| 117 for d in olddirs: | |
| 118 if d not in newdirs and _isdir(svn, branchpath, d): | |
| 119 deleted.append(d) | |
| 120 | |
| 121 return added, deleted | |
| 122 | |
| 123 | |
| 124 def _externals(ctx): | |
| 125 ext = svnexternals.externalsfile() | |
| 126 if '.hgsvnexternals' in ctx: | |
| 127 ext.read(ctx['.hgsvnexternals'].data()) | |
| 128 return ext | |
| 129 | |
| 130 | |
| 131 def commit_from_rev(ui, repo, rev_ctx, hg_editor, svn_url, base_revision | |
| 132 username, password): | |
| 133 """Build and send a commit from Mercurial to Subversion. | |
| 134 """ | |
| 135 file_data = {} | |
| 136 svn = svnwrap.SubversionRepo(svn_url, username, password) | |
| 137 parent = rev_ctx.parents()[0] | |
| 138 parent_branch = rev_ctx.parents()[0].branch() | |
| 139 branch_path = 'trunk' | |
| 140 | |
| 141 if parent_branch and parent_branch != 'default': | |
| 142 branch_path = 'branches/%s' % parent_branch | |
| 143 | |
| 144 extchanges = list(svnexternals.diff(_externals(parent), | |
| 145 _externals(rev_ctx))) | |
| 146 addeddirs, deleteddirs = _getdirchanges(svn, branch_path, parent, rev_ctx, | |
| 147 rev_ctx.files(), extchanges) | |
| 148 deleteddirs = set(deleteddirs) | |
| 149 | |
| 150 props = {} | |
| 151 copies = {} | |
| 152 for file in rev_ctx.files(): | |
| 153 if file == '.hgsvnexternals': | |
| 154 continue | |
| 155 new_data = base_data = '' | |
| 156 action = '' | |
| 157 if file in rev_ctx: | |
| 158 fctx = rev_ctx.filectx(file) | |
| 159 new_data = fctx.data() | |
| 160 | |
| 161 if 'x' in fctx.flags(): | |
| 162 props.setdefault(file, {})['svn:executable'] = '*' | |
| 163 if 'l' in fctx.flags(): | |
| 164 props.setdefault(file, {})['svn:special'] = '*' | |
| 165 | |
| 166 if file not in parent: | |
| 167 renamed = fctx.renamed() | |
| 168 if renamed: | |
| 169 # TODO current model (and perhaps svn model) does not support | |
| 170 # this kind of renames: a -> b, b -> c | |
| 171 copies[file] = renamed[0] | |
| 172 base_data = parent[renamed[0]].data() | |
| 173 | |
| 174 action = 'add' | |
| 175 dirname = '/'.join(file.split('/')[:-1] + ['']) | |
| 176 else: | |
| 177 base_data = parent.filectx(file).data() | |
| 178 if ('x' in parent.filectx(file).flags() | |
| 179 and 'x' not in rev_ctx.filectx(file).flags()): | |
| 180 props.setdefault(file, {})['svn:executable'] = None | |
| 181 if ('l' in parent.filectx(file).flags() | |
| 182 and 'l' not in rev_ctx.filectx(file).flags()): | |
| 183 props.setdefault(file, {})['svn:special'] = None | |
| 184 action = 'modify' | |
| 185 else: | |
| 186 pos = file.rfind('/') | |
| 187 if pos >= 0: | |
| 188 if file[:pos] in deleteddirs: | |
| 189 # This file will be removed when its directory is removed | |
| 190 continue | |
| 191 action = 'delete' | |
| 192 file_data[file] = base_data, new_data, action | |
| 193 | |
| 194 def svnpath(p): | |
| 195 return '%s/%s' % (branch_path, p) | |
| 196 | |
| 197 changeddirs = [] | |
| 198 for d, v1, v2 in extchanges: | |
| 199 props.setdefault(svnpath(d), {})['svn:externals'] = v2 | |
| 200 if d not in deleteddirs and d not in addeddirs: | |
| 201 changeddirs.append(svnpath(d)) | |
| 202 | |
| 203 # Now we are done with files, we can prune deleted directories | |
| 204 # against themselves: ignore a/b if a/ is already removed | |
| 205 deleteddirs2 = list(deleteddirs) | |
| 206 deleteddirs2.sort(reverse=True) | |
| 207 for d in deleteddirs2: | |
| 208 pos = d.rfind('/') | |
| 209 if pos >= 0 and d[:pos] in deleteddirs: | |
| 210 deleteddirs.remove(d[:pos]) | |
| 211 | |
| 212 newcopies = {} | |
| 213 for source, dest in copies.iteritems(): | |
| 214 newcopies[svnpath(source)] = (svnpath(dest), base_revision) | |
| 215 | |
| 216 new_target_files = [svnpath(f) for f in file_data] | |
| 217 for tf, ntf in zip(file_data, new_target_files): | |
| 218 if tf in file_data: | |
| 219 file_data[ntf] = file_data[tf] | |
| 220 if tf in props: | |
| 221 props[ntf] = props[tf] | |
| 222 del props[tf] | |
| 223 if hgutil.binary(file_data[ntf][1]): | |
| 224 props.setdefault(ntf, {}).update(props.get(ntf, {})) | |
| 225 props.setdefault(ntf, {})['svn:mime-type'] = 'application/octet-stream' | |
| 226 del file_data[tf] | |
| 227 | |
| 228 addeddirs = [svnpath(d) for d in addeddirs] | |
| 229 deleteddirs = [svnpath(d) for d in deleteddirs] | |
| 230 new_target_files += addeddirs + deleteddirs + changeddirs | |
| 231 if not new_target_files: | |
| 232 raise NoFilesException() | |
| 233 try: | |
| 234 svn.commit(new_target_files, rev_ctx.description(), file_data, | |
| 235 base_revision, set(addeddirs), set(deleteddirs), | |
| 236 props, newcopies) | |
| 237 except core.SubversionException, e: | |
| 238 if hasattr(e, 'apr_err') and (e.apr_err == core.SVN_ERR_FS_TXN_OUT_OF_DATE | |
| 239 or e.apr_err == core.SVN_ERR_FS_CONFLICT): | |
| 240 raise hgutil.Abort('Base text was out of date, maybe rebase?') | |
| 241 else: | |
| 242 raise |
