1"""hgpushsvn must be run in a repository created by hgimportsvn. It pushes 2local Mercurial changesets one by one or optionally collapsed into a single 3commit back to the SVN repository. 4 5""" 6 7import codecs 8import os 9import stat 10import sys 11import re 12from optparse import OptionParser 13import copy 14import traceback 15 16from hgsvn.shell import (once_or_more 17 , get_encoding 18 ) 19from hgsvn.hgclient import * 20from hgsvn import ui 21from hgsvn.common import * 22from hgsvn.errors import * 23from hgsvn.run.common import run_parser, display_parser_error 24from hgsvn.run.common import locked_main 25from hgsvn.svnclient import * 26 27import six 28from six import print_ 29 30svn_branch = "" 31repos_url = "" 32 33def IsDebug(): 34 return (ui._level >= ui.DEBUG) 35 36 37def get_hg_revs(first_rev, svn_branch, last_rev="tip"): 38 """ 39 Get a chronological list of revisions (changeset IDs) between the two 40 revisions (included). 41 """ 42 args = ["log", "--template", r'{rev}:{node|short}\n', "-b", svn_branch, 43 '--limit', '1000', 44 "-r", "%s::%s" % (strip_hg_rev(first_rev), 45 strip_hg_rev(last_rev))] 46 out = run_hg(args) 47 return [strip_hg_rev(s) for s in out.splitlines()] 48 49class hglog_node(object): 50 parents = [] 51 childs = [] 52 rev_no = -1 53 mark = None 54 55 def __repr__(self): 56 return "parents:%s ; childs: %s ; rev:%s\n"%(self.parents, self.childs, self.rev_no) 57 58class hglog_tree(): 59 hg_tree = None 60 revs = None 61 can_merge_anonims = False 62 63 def __init__(self, tree, revs): 64 self.hg_tree = tree 65 self.revs = revs 66 67 def close(self): 68 self.hg_tree = None 69 self.revs = None 70 71 def clean_revs_after(self, rev_id, safe_mark = -1): 72 ui.status("clean hg history after rev%s marked by parent %s "%(rev_id, safe_mark), level = ui.DEBUG) 73 rev_stop = self.hg_tree[rev_id].rev_no 74 drop_revs = list() 75 for rev in self.revs: 76 node = self.hg_tree[rev] 77 rev_no = node.rev_no 78 if rev_no > rev_stop: 79 if node.mark != safe_mark: 80 ui.status("remove hg history rev%s"%rev, level = ui.DEBUG) 81 drop_revs.append(rev); 82 83 for rev in drop_revs: 84 del self.hg_tree[rev] 85 self.revs.remove(rev) 86 87 def anonim_mark_safe_childs(self, safe_rev, mark): 88 ui.status("anonims mark childs for rev%s by parent %s "%(rev_id, safe_mark), level = ui.TRACE) 89 #recursively mark all descendants 90 safenode = self.hg_tree[safe_rev] 91 safe_no = safenode.rev_no 92 childs = safenode.childs 93 for child in childs: 94 childid = strip_hg_revid(child) 95 self.anonim_mark_safe_childs(childid, mark) 96 safenode.mark = mark 97 98 def anonim_remove(self, child, not_parent): 99 ui.status("anonims removes for child %s"%child, level = ui.DEBUG) 100 #remove childs that have no parent not_parent 101 self.anonim_mark_safe_childs(not_parent, not_parent) 102 #now remove all nonmarked childs 103 self.clean_revs_after(not_parent) 104 105 def anonim_check_start(self, rev_id): 106 ui.status("anonim check for %s"%rev_id, level = ui.PARSE) 107 #if IsDebug(): 108 # print "hg log is %s"%self.hg_tree 109 node = self.hg_tree[rev_id] 110 if len(node.childs) > 1: 111 #check that only one parent of all are in revs 112 childs_in_branch = None 113 for child in node.childs: 114 childid = strip_hg_revid(child) 115 if childid in self.revs: 116 if childs_in_branch is None: 117 childs_in_branch = childid 118 else: 119 if self.can_merge_anonims : 120 #remove alternate anonimous branch from tree if uses soft merge-push 121 self.anonim_remove(childid, childs_in_branch) 122 else: 123 ui.status("anonimous branch parent detected on hg:%s so stop on it"%rev_id) 124 self.clean_revs_after(rev_id) 125 #raise HgSVNError("anonimous branch parent detected on hg:%s" % rev_id) 126 127 def clean_anonims(self, can_merge_anonims): 128 if IsDebug(): 129 print_("clean hystory tree:") 130 for node in self.hg_tree.items(): 131 print_(node) 132 print_("hystory set:") 133 for node in self.revs: 134 print_(node) 135 136 self.can_merge_anonims = can_merge_anonims 137 #now check tree for anonimous branches 138 for rev_id in self.revs: 139 self.anonim_check_start( rev_id ); 140 141 142def get_hg_revs_with_graph(first_rev, svn_branch, last_rev="tip", opts = None ): 143 """ 144 Get a chronological list of revisions (changeset IDs) between the two 145 revisions (included). 146 tree contains nodes description as [parents, childs] 147 """ 148 args = ["log", "--template", r'{rev}:{node|short};{parents};{children}\n', "-b", svn_branch, 149 '--limit', '1000', 150 "-r", "%s::%s" % (strip_hg_rev(first_rev), 151 strip_hg_rev(last_rev))] 152 out = run_hg(args) 153 lines = [s.strip() for s in out.splitlines()] 154 revs = list() 155 hg_tree = dict() 156 parents = [] 157 childs = [] 158 159 can_merge_anonims = False 160 if opts: 161 can_merge_anonims = opts.force or (opts.merge_branches=="skip") or (opts.merge_branches=="trypush") 162 163 for line in lines: 164 (rev, parent, child) = re.split(";",line) 165 rev_no, rev_id = re.split(":", rev) 166 revs.append(rev_id) #strip_hg_rev(rev)); 167 node = hglog_node() 168 node.parents = re.split(" ",parent.strip()); 169 node.childs = re.split(" ",child.strip()); 170 node.rev_no = rev_no 171 hg_tree[rev_id] = node 172 173 tree = hglog_tree(hg_tree, revs) 174 tree.clean_anonims(can_merge_anonims) 175 tree.close() 176 177 if IsDebug(): 178 print_("hg have hystory tree:") 179 for node in hg_tree.items(): 180 print_(node) 181 return revs, hg_tree 182 183def is_anonimous_branch_head(rev, hg_tree): 184 """ 185 checks chat revision have more than one child in branch 186 """ 187 if not (rev in hg_tree): 188 return False 189 childs = hg_tree[rev].childs 190 if len(childs) <= 1: 191 return False 192 ChildsInTree = 0 193 for s in childs: 194 if strip_hg_rev(s) in hg_tree: 195 ChildsInTree = ChildsInTree+1 196 return (ChildsInTree > 1) 197 198def get_pairs(L): 199 """ 200 Given a list, return a list of consecutive pairs of values. 201 """ 202 return [(L[i], L[i+1]) for i in range(len(L) - 1)] 203 204def get_hg_changes(start_rev, end_rev): 205 """ 206 Get paths of changed files from a previous revision. 207 Returns a tuple of (added files, removed files, modified files, copied files) 208 Copied files are dict of (dest=>src), others are lists. 209 """ 210 if IsDebug(): 211 ui.status("get_hg_changes for hg: %s::%s"%(start_rev, end_rev)) 212 args = ["st", "-armC"] 213 if start_rev != end_rev: 214 rev_string = "%s::%s"%(start_rev, end_rev) 215 args += ["--rev", rev_string] 216 else: 217 args += ["--change", end_rev] 218 out = run_hg(args, output_is_locale_encoding=True) 219 added = [] 220 removed = [] 221 modified = [] 222 copied = {} 223 skipnext = False 224 for line in out.splitlines(): 225 st = line[0] 226 path = line[2:] 227 if (is_ignore4svn(path)): continue 228 if st == 'A': 229 added.append(path) 230 lastadded=path 231 elif st == 'R': 232 removed.append(path) 233 elif st == 'M': 234 modified.append(path) 235 elif st == ' ': 236 added.remove(lastadded) 237 copied[lastadded] = path 238 #print "added:", added 239 #print "removed:", removed 240 #print "modified:", modified 241 return added, removed, modified, copied 242 243def cleanup_list(target, drops): 244 for x in drops: 245 if x in target: 246 target.remove(x) 247 248def cleanup_svn_type(files, svn_status=None, state='unversioned'): 249 """ 250 removes svn:<state> entry from files, status=<None> - removes all recognised in svn entries 251 """ 252 if svn_status is None: 253 svn_status = get_svn_status(".") 254 stated = list() 255 for entry in svn_status: 256 if (entry['path'] in files): 257 if (state is None) or (entry['type'] == state): 258 files.remove(entry['path']) 259 stated.append(entry['path']) 260 return files, stated 261 262def cleanup_svn_status(files, svn_status=None, state='unversioned'): 263 """ 264 removes svn:<state> entry from files, status=<None> - removes all recognised in svn entries 265 """ 266 if svn_status is None: 267 svn_status = get_svn_status(".") 268 stated = list() 269 for entry in svn_status: 270 if (entry['path'] in files): 271 if (state is None) or (entry['status'] == state): 272 files.remove(entry['path']) 273 stated.append(entry['path']) 274 return files, stated 275 276def cleanup_svn_notatype(files, svn_status=None, state='unversioned'): 277 """ 278 leaves only svn:<state> entry in files, status=<None> - leaves all recognised in svn entries 279 """ 280 if svn_status is None: 281 svn_status = get_svn_status(".") 282 nostatedfile = list() 283 statedfile = list() 284 for entry in svn_status: 285 if (entry['path'] in files): 286 files.remove(entry['path']) 287 if (state is None) or (entry['type'] == state): 288 statedfile.append(entry['path']) 289 else: 290 nostatedfile.append(entry['path']) 291 nostatedfile.extend(files) 292 del files[:] 293 files.extend(statedfile) 294 return files, nostatedfile 295 296def cleanup_svn_versioned(files, svn_status=None): 297 files, verfiles = cleanup_svn_notatype(files, svn_status, 'unversioned') 298 verfiles, absentfiles = cleanup_svn_notatype(verfiles, svn_status, None) 299 files.extend(absentfiles) 300 return files, verfiles 301 302def cleanup_svn_unversioned(files, use_svn_status=None): 303 files, unverfiles = cleanup_svn_type(files, use_svn_status, 'unversioned') 304 files, absentfiles = cleanup_svn_notatype(files, use_svn_status, None) 305 unverfiles.extend(absentfiles) 306 return files, unverfiles 307 308def svn_unversioned(files, svn_status = None): 309 if svn_status is None: 310 svn_status = get_svn_status(".") 311 res = list([]) 312 for entry in svn_status: 313 if(entry['type'] == 'unversioned') and entry['path'] in files: 314 res.append(entry['path']) 315 return res 316 317def get_ordered_dirs(l): 318 if l is None : 319 return [] 320 """ 321 Given a list of relative file paths, return an ordered list of dirs such that 322 creating those dirs creates the directory structure necessary to hold those files. 323 """ 324 dirs = set() 325 for path in l: 326 while True: 327 if not path : 328 break 329 path = os.path.dirname(path) 330 if not path or path in dirs: 331 break 332 dirs.add(path) 333 return list(sorted(dirs)) 334 335def get_hg_csets_description(start_rev, end_rev): 336 """Get description of a given changeset range.""" 337 return run_hg(["log", "--template", r"{desc|strip}\n", "--follow" 338 , "--branch", svn_branch 339 , "--rev", start_rev+":"+end_rev, "--prune", start_rev]) 340 341def get_hg_cset_description(rev_string): 342 """Get description of a given changeset.""" 343 return run_hg(["log", "--template", "{desc|strip}", "-r", rev_string]) 344 345def get_hg_log(start_rev, end_rev): 346 """Get log messages for given changeset range.""" 347 348 log_args=["log", "--verbose", "--follow", "--rev", start_rev+"::"+end_rev, "--prune", start_rev] 349 return run_hg(log_args) 350 351def get_svn_subdirs(top_dir): 352 """ 353 Given the top directory of a working copy, get the list of subdirectories 354 (relative to the top directory) tracked by SVN. 355 """ 356 subdirs = [] 357 def _walk_subdir(d): 358 svn_dir = os.path.join(top_dir, d, ".svn") 359 if os.path.exists(svn_dir) and os.path.isdir(svn_dir): 360 subdirs.append(d) 361 for f in os.listdir(os.path.join(top_dir, d)): 362 d2 = os.path.join(d, f) 363 if f != ".svn" and os.path.isdir(os.path.join(top_dir, d2)): 364 _walk_subdir(d2) 365 _walk_subdir(".") 366 return subdirs 367 368def strip_nested_removes(targets): 369 """Strip files within removed folders and return a cleaned up list.""" 370 # We're going a safe way here: "svn info" fails on items within removed 371 # dirs. 372 # TODO - it is very slooow design, maybe more optimal strategy can be? 373 clean_targets = [] 374 for target in targets: 375 try: 376 run_svn(['info', '--xml', target], mask_atsign=True) 377 except ExternalCommandFailed as err: 378 ui.status(str(err), level=ui.DEBUG) 379 continue 380 clean_targets.append(target) 381 return clean_targets 382 383def adjust_executable_property(files, svn_status = None): 384 execflags = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH 385 svn_map = {} 386 check_files = files 387 388 if svn_status is not None: 389 #if have status cached, then use it to filter only files that have props 390 check_files = [] 391 for entry in svn_status: 392 if (entry['path'] in files): 393 if (entry['props'] != "none"): 394 check_files.append(entry['path']) 395 if IsDebug(): 396 print_("adjust_executable_property: select files with props:%s" % check_files) 397 398 for fname in check_files: 399 if svn_get_prop('svn:executable', fname).strip(): 400 svn_map[fname] = True 401 else: 402 svn_map[fname] = False 403 for fname in check_files: 404 m = os.stat(fname).st_mode & 0o777 405 is_exec = bool(m & execflags) 406 actual_exec = fname in svn_map; 407 if actual_exec: 408 actual_exec = svn_map[fname]; 409 if is_exec and not actual_exec: 410 run_svn(['propset', 'svn:executable', 'ON', fname], 411 mask_atsign=True) 412 elif not is_exec and actual_exec: 413 run_svn(['propdel', 'svn:executable', fname], mask_atsign=True) 414 415def do_svn_copy(src, dest, alredy_added): 416 """ 417 Call Svn copy command to record copying file src to file dest. 418 If src is present then backup src before other tasks. 419 Before issuing copy command move dest to src and remove src after 420 """ 421 backup_src = '' 422 added = list() 423 if os.path.exists(src): 424 backup_src = os.path.join(hgsvn_private_dir, "hgpushsvn_backup.tmp") 425 os.rename(src, backup_src) 426 427 try: 428 try: 429 # We assume that src is cotrolled by svn 430 os.rename(dest, src) 431 432 # Create svn subdirectories if needed 433 # We know that subdirectories themselves are present 434 # as dest is present 435 pattern = re.compile(r"[/\\]") 436 pos = 0 437 while(True): 438 match = pattern.search(dest, pos + 1) 439 if match == None: 440 break 441 442 pos = match.start() 443 path = dest[:pos] 444 try: 445 if not (path in alredy_added): 446 run_svn(['add', '--depth=empty'], [path], mask_atsign=True) 447 alredy_added.append(path) 448 added.append(path) 449 except (ExternalCommandFailed) as e: 450 if not str(e).find("is already under version control") > 0: 451 raise 452 pos = match.end() - 1 453 454 # Do the copy itself 455 run_svn(['copy', src, dest], mask_atsign=True) 456 except ExternalCommandFailed: 457 # Revert rename 458 os.rename(src, dest) 459 raise 460 finally: 461 if os.path.isfile(src): 462 os.remove(src) 463 464 if(backup_src): 465 os.rename(backup_src, src) 466 return added 467 468def hg_push_svn(start_rev, end_rev, edit, username, password, cache, options, use_svn_wc = False): 469 """ 470 Commit the changes between two hg revisions into the SVN repository. 471 Returns the SVN revision object, or None if nothing needed checking in. 472 use_svn_wc - prevents reverting wc state to start_rev, and thus accepts changes that 473 prepared here 474 """ 475 476 # Before replicating changes revert directory to previous state... 477 run_hg(['revert', '--all', '--no-backup', '-r', end_rev]) 478 479 if not use_svn_wc: 480 # ... and restore .svn directories, if we lost some of them due to removes 481 run_svn(['revert', '--recursive', '.']) 482 483 # Do the rest over up-to-date working copy 484 # Issue 94 - moved this line to prevent do_svn_copy crashing when its not the first changeset 485 run_hg(["up", "-C", "-r", end_rev]) 486 487 added, removed, modified, copied = get_hg_changes(start_rev, end_rev) 488 489 svn_status_cache = get_svn_status(".") 490 491 added, modified_add = cleanup_svn_status(added, svn_status_cache, 'modified') 492 added, alredy_add = cleanup_svn_status(added, svn_status_cache, 'normal') 493 cleanup_svn_unversioned(removed, svn_status_cache) 494 495 modified, unversioned_change = cleanup_svn_unversioned(modified, svn_status_cache) 496 if unversioned_change: 497 print_("this changes %s unverioned in svn" % unversioned_change) 498 if options.on_noversioned_change == "add" : 499 added.extend(unversioned_change) 500 elif options.on_noversioned_change == "skip": 501 unversioned_change = None #this is dummy - for skip choose nothig to do 502 else: 503 raise HgSVNError("unckonwn action %s for unversioned changes" % options.on_noversioned_change) 504 505 if modified_add: 506 ui.status("this added files alredy versioned in svn:%s" 507 " just state it as modify" % modified_add) 508 modified.extend(modified_add) 509 510 cleanup_svn_versioned(added, svn_status_cache) #drop alredy versioned nodes from addition 511 512 if alredy_add: 513 ui.status("this added %s alredy verioned in svn" % alredy_add) 514 modified.extend(alredy_add) #and place them to modified 515 516 # Record copyies into svn 517 for dest, src in six.iteritems(copied): 518 copy_added = do_svn_copy(src,dest, alredy_add) 519 modified.extend(copy_added) 520 521 # Add new files and dirs 522 if added: 523 ui.status("this added %s alredy verioned in svn" % alredy_add) 524 svnadd = list(added) 525 cleanup_svn_status(svnadd, svn_status_cache, 'added') #not add alredy added filess 526 #cleanup_list(svnadd, alredy_add) 527 if svnadd: 528 bulk_args = cleanup_svn_versioned(get_ordered_dirs(svnadd), svn_status_cache)[0] 529 bulk_args += svnadd 530 cleanup_list(bulk_args, alredy_add) 531 if len(bulk_args) > 0: 532 run_svn(["add", "--depth=empty"], bulk_args, mask_atsign=True) 533 534 if IsDebug(): 535 print_("svn to add:", added) 536 print_("svn to remov:", removed) 537 print_("svn to modify:", modified) 538 539 # Remove old files and empty dirs 540 if removed: 541 svnremove = list(removed) 542 cleanup_svn_type(svnremove, svn_status_cache, 'removed') #drop alredy removed nodes from removing 543 empty_dirs = [d for d in reversed(get_ordered_dirs(svnremove)) 544 if not run_hg(["st", "-c", "%s" %d])] 545 # When running "svn rm" all files within removed folders needs to 546 # be removed from "removed". Otherwise "svn rm" will fail. For example 547 # instead of "svn rm foo/bar foo" it should be "svn rm foo". 548 # See issue15. 549 svn_removed = strip_nested_removes(svnremove + empty_dirs) 550 if svn_removed: 551 run_svn(["rm"], svn_removed, mask_atsign=True) 552 if added or removed or modified or copied: 553 svn_sep_line = "--This line, and those below, will be ignored--" 554 adjust_executable_property(added+modified, svn_status_cache) # issue24 555 description = get_hg_csets_description(start_rev, end_rev) 556 if (ui._level >= ui.PARSE): 557 ui.status("use svn commit message:\n%s"%description, level=ui.PARSE); 558 fname = os.path.join(hgsvn_private_dir, 'commit-%s.txt' % end_rev) 559 lines = description.splitlines()+[""] 560 lines.append(svn_sep_line) 561 lines.append("To cancel commit just delete text in top message part") 562 lines.append("") 563 lines.append("Changes to be committed into svn:") 564 for item in svn_status_cache: 565 if item['type'] != 'unversioned': 566 lines.append( "%s %s" % (item['type'], item['path'])) 567 lines.append("") 568 lines.append(("These changes are produced from the following " 569 "Hg changesets:")) 570 lines.extend(get_hg_log(start_rev, end_rev).splitlines()) 571 f = codecs.open(fname, "wb", "utf-8") 572 f.write(os.linesep.join(lines)) 573 f.close() 574 575 try: 576 if edit: 577 editor=(os.environ.get("HGEDITOR") or 578 os.environ.get("SVNEDITOR") or 579 os.environ.get("VISUAL") or 580 os.environ.get("EDITOR", "vi")) 581 582 rc = os.system("%s \"%s\"" % (editor, fname) ) 583 if(rc): 584 raise ExternalCommandFailed("Can't launch editor") 585 586 empty = True 587 588 f=open(fname, "r") 589 for line in f: 590 if(line == svn_sep_line): 591 break 592 593 if(line.strip() != ""): 594 empty = False 595 break 596 f.close() 597 598 if(empty): 599 raise EmptySVNLog("Commit cancelled by user\n") 600 601 svn_rev = None 602 svn_commands = ["commit", "--encoding", "utf-8", "-F", fname ]#get_encoding()] 603 if username is not None: 604 svn_commands += ["--username", username] 605 if password is not None: 606 svn_commands += ["--password", password] 607 if cache: 608 svn_commands.append("--no-auth-cache") 609 out = run_svn(svn_commands) 610 611 if IsDebug(): 612 print_("svn commit:%s" % out) 613 614 svn_rev = None 615 try: 616 outlines = out.splitlines(True) 617 outlines.reverse() 618 for binline in outlines: 619 line = svn_decode(binline); 620 # one of the last lines holds the revision. 621 # The previous approach to set LC_ALL to C and search 622 # for "Committed revision XXX" doesn't work since 623 # svn is unable to convert filenames containing special 624 # chars: 625 # http://svnbook.red-bean.com/en/1.2/svn.advanced.l10n.html 626 match = re.search("(\d+)", line) 627 if match: 628 svn_rev = int(match.group(1)) 629 break 630 finally: 631 if svn_rev is None: 632 #if retrieve revision from commit report fails? do it with additional svn request 633 svn_info = get_svn_info(".") 634 svn_rev = svn_info["revision"] 635 636 637 return svn_rev 638 finally: 639 # Exceptions are handled by main(). 640 if (ui._level < ui.PARSE): 641 os.remove(fname) 642 return None 643 else: 644 ui.status("*", "svn: nothing to do") 645 return -1 646 647def svn_merge_proof(args): 648 try: 649 return run_svn(args) 650 except (RunCommandError) as e: 651 if e.err_have("Cannot merge into mixed-revision"): 652 # on this error try to update to current svnrev, with potential externals, 653 # or others mixed-versions 654 svn_info = get_svn_info(".") 655 svn_current_rev = svn_info["revision"] #last_changed_rev 656 #run_svn(['up', '-r', svn_current_rev]); 657 svn_switch_to(svn_info["url"], 'HEAD', clean=False, ignore_ancetry=True, propagate_rev=True) 658 return run_svn(args) 659 else: 660 raise 661 662def svn_merge_2wc(rev_number, svn_base, svnacception = "working"): 663 args = ["merge","--non-interactive","--accept=%s"%svnacception] 664 if rev_number is not None: 665 svn_base += '@%d'%rev_number 666 ui.status("merging %s" % (svn_base), level=ui.VERBOSE) 667 svn_base = svn_base.lstrip('/\\'); 668 args += [repos_url +'/'+svn_base] 669 return svn_merge_proof(args) 670 671def svn_merge_range_2wc(svn_base, rev_first, rev_last, svnacception = "working"): 672 args = ["merge","--non-interactive","--accept=%s"%svnacception] 673 svn_base = svn_base.lstrip('/\\'); 674 svn_target = svn_base; 675 svn_range = '%d:%d'%(rev_first, rev_last) 676 ui.status("merging %s with %s" % (svn_target, svn_range), level=ui.VERBOSE) 677 try: 678 result = svn_merge_proof(args + ["-r", svn_range , repos_url +'/'+svn_target]) 679 except: 680 ui.status("failed merge revrange, so try merge different tree " % (svn_target, svn_range), level=ui.ALERT) 681 svn_revert_all(); 682 svn_first = '%s@%d'%(svn_base, rev_first); 683 svn_last = '%s@%d'%(svn_base, rev_last); 684 ui.status("merging %s : %s" % (svn_first, svn_last), level=ui.VERBOSE) 685 args += ["--ignore-ancestry"] 686 result = svn_merge_proof(args + [repos_url +'/'+svn_first, repos_url +'/'+svn_last]) 687 return result 688 689def hg_sync_merge_svn(start_rev, rev, hg_tree, target_hgbranch, options): 690 # start_rev - parent rev on wich merges rev 691 # rev - mergepoint rev 692 # check all parents for branch name, and try to push one branches if can 693 # after all branches complete, merge svn work copy and resolves it for hg_push_svn 694 695 # Before replicating changes revert directory to previous state... 696 run_hg(['revert', '--all', '--no-backup', '-r', start_rev]) 697 # ... and restore .svn directories, if we lost some of them due to removes 698 run_svn(['revert', '--recursive', '.']) 699 700 parents = hg_tree[rev].parents 701 if len(parents) <= 1: 702 return True 703 704 if IsDebug(): 705 print_("merge for parents:%s"%parents) 706 branches = dict() 707 708 for s in parents: 709 r = strip_hg_rev(s) 710 branchname = target_hgbranch 711 BranchSVNRev = None 712 if not (r in hg_tree): 713 branchname, BranchSVNRev = get_branchinfo_from_hg(s) 714 715 headinfo = [s,BranchSVNRev] 716 if branchname in branches: 717 branches[branchname].extend(headinfo) 718 else: 719 branches[branchname] = [headinfo] 720 if IsDebug(): 721 print_("merge have branches:") 722 for node in branches.items(): 723 print_(node) 724 725 for bp in branches.values(): 726 #check that all parent is one from its branch 727 if len(bp) > 1: 728 ui.status("Mercurial repository rev:%s have multiple parents %s of branch %s," 729 " cant distinguish how to push it" % (s, bp, target_hgbranch) 730 ) 731 return False 732 733 if target_hgbranch in branches: 734 del branches[target_hgbranch] 735 736 branch_affected =False 737 merge_svn_revs = [] 738 branch_switched = False 739 current_branch = target_hgbranch 740 for node in branches.items(): 741 use_branch = node[0] 742 head = node[1][0] 743 hg_parent = head[0] 744 svn_parent = head[1] 745 svn_base = svn_base_of_branch(use_branch) 746 if svn_base is None: 747 ui.status("rev:%s need push for parent %s of branch %s, that is uncknown location in svn" % (rev, hg_parent, use_branch )) 748 return False 749 if svn_parent is None: 750 #there is no svn tag attached 751 # this is unversioned head, so its branch should be pushed for one rev 752 ui.status("rev:%s need push for parent %s of branch hg:%s svn:%s" % (rev, hg_parent, use_branch, svn_base )) 753 try: 754 current_branch = use_branch 755 if hg_push_branch(options, hg_parent, use_branch, svn_base) != 0: 756 raise HgSVNError("failed to push branch <%s>" % use_branch) 757 svn_parent = get_svn_rev_from_hg(strip_hg_rev(hg_parent)) 758 merge_svn_revs.append([svn_parent, hg_parent, use_branch, svn_base]) 759 except: 760 if not options.force: 761 raise HgSVNError("failed to push branch <%s>" % use_branch) 762 else: 763 merge_svn_revs.append([svn_parent, hg_parent, use_branch, svn_base]) 764 765 766 if current_branch != target_hgbranch: #branch_switched: 767 ui.status("switch from branch %s back to %s " % (current_branch, target_hgbranch)) 768 svn_base = svn_base_of_branch(target_hgbranch) 769 svn_switch_to(repos_url +'/'+svn_base, clean=True, ignore_ancetry=True) 770 hg_force_switch_branch(target_hgbranch, start_rev) 771 772 if len(merge_svn_revs) == 0: 773 return options.force 774 775 #now all parents is ok, lets merge it 776 #do the merge in svn rev order 777 if len(merge_svn_revs) > 1: 778 ui.status("sorting list %s"% merge_svn_revs) 779 merge_svn_revs.sort(key=lambda rev_node: rev_node[0]) 780 for elem in merge_svn_revs: 781 try: 782 svn_merge_2wc(elem[0], elem[3], svnacception = options.svnacception) 783 except: 784 #since svn normal merge fails? try to merge revs range 785 #try to find last common incoming rev 786 ui.status("fail to merge svn:%s"%(elem[0]), loglevel = ui.ALERT); 787 788 args = ["log", "--template", r'{rev}:{node|short}\n', 789 "-r", "last(ancestors(%s) and branchpoint() and branch(\"%s\"))" % (strip_hg_rev(start_rev), elem[2]) 790 ] 791 last_branch_incoming_rev = run_hg(args) 792 if len(last_branch_incoming_rev) <= 0 : 793 # there is no merge-in from desired branch, therefore merge from it`s base rev 794 args = ["log", "--template", r'{rev}:{node|short}\n', 795 "-r", "first(branch(%s))" % (elem[2]) 796 ] 797 last_branch_incoming_rev = run_hg(args) 798 if len(last_branch_incoming_rev) <= 0 : 799 return options.force 800 801 ui.status("try merge revs range from hg:%s"%(last_branch_incoming_rev), level=ui.VERBOSE); 802 last_branch_incoming_svnrev = get_svn_rev_from_hg( get_hg_cset(last_branch_incoming_rev) ); 803 if (last_branch_incoming_svnrev is None): 804 ui.status("hg:%s not synched 2 svn"%(last_branch_incoming_rev), level=ui.WARNING); 805 return options.force 806 807 ui.status("try merge revs range from svn:%s to svn:%s"%(last_branch_incoming_svnrev, elem[0]), level=ui.VERBOSE); 808 svn_merge_range_2wc(elem[3], last_branch_incoming_svnrev, elem[0], svnacception = options.svnacception) 809 810 #now resolve all conflicts 811 svn_resolve_all() 812 return True 813 814def autostart_branch(hg_branch, options): 815 ui.status("svn_branch of hg:%s absent. so try to tag it from branch origin"%hg_branch, level=ui.VERBOSE) 816 svn_sync = get_svn_origin_of_hgbranch(hg_branch) 817 ui.status("at sync:%s"%svn_sync, level=ui.DEBUG) 818 svn_branch = svn_base_of_branch(hg_branch) 819 if (not (svn_sync.svnrev is None)) or (svn_sync.hgrev is None): 820 ui.status("svn %s is confused with hg %s. cant determine what to do, try to define valid svn.NNN tags according to svn-branch"%(svn_branch, hg_branch), level=ui.ERROR ) 821 raise HgSVNError("cant autostart branch %s"%hg_branch) 822 orig_hgrev = strip_hg_rev(svn_sync.hgrev) 823 824 if not (svn_sync.svn_base is None): 825 ui.status("looks svn have alredy branch %s@%s created, try sync it`s base"%(svn_branch, svn_base) , level = ui.NOTE) 826 map_svn_rev_to_hg(svn_sync.svn_base, orig_hgrev, local=True) 827 svn_switch_to(repos_url+svn_branch+"@%s"%svn_sync.svn_base, ignore_ancetry=True, clean=True) 828 return 0 829 830 orig_branch = get_branch_from_hg(orig_hgrev) 831 svn_rev = svn_sync.svn_src 832 if (svn_sync.svn_src is None): 833 ui.status("looks even origin of hg:%s absent, try to autostart ones origin %s@%s"%(hg_branch, orig_branch, orig_hgrev), level=ui.NOTE) 834 hg_push_branch(options, orig_hgrev, orig_branch) 835 svn_rev = get_svn_rev_from_hg(orig_hgrev) 836 837 orig_svn_branch = svn_base_of_branch(orig_branch) 838 839 ui.status("reaches origin hg:%s, now try to tag it from svn:%s@%s"%(hg_branch, orig_svn_branch, svn_rev), level=ui.WARNING ) 840 automessage = '"start hg:%s here. [hg:%s@%s]"'%(hg_branch, orig_branch, orig_hgrev) 841 run_svn(['copy', '--parents', '-m', automessage, "%s@%s"%(repos_url+orig_svn_branch, svn_rev), repos_url+svn_branch]) 842 svn_origin = svn_branch_revset() 843 svn_origin.eval_path('^'+svn_branch) 844 if (svn_origin.baserev == svn_origin.headrev) and (svn_origin.srcrev == svn_rev) and (svn_origin.source == orig_svn_branch): 845 map_svn_rev_to_hg(svn_origin.baserev, orig_hgrev, local=True) 846 svn_switch_to(repos_url+svn_branch, ignore_ancetry=True, clean=True) 847 else: 848 ui.status("at sync:%s"%svn_origin, level=ui.DEBUG) 849 ui.status("failed to make sync hg:%s at svn %s , orinated from hg:%s@%s"%(hg_branch, svn_branch, orig_branch, orig_hgrev), level=ui.ERROR) 850 return -1 851 return 0 852 853def hg_push_branch(options, hg_parent, use_branch = None, svn_base = None): 854 # hg_parent - hgrevision of requred branch 855 # this is unversioned head, so its branch should be pushed for one rev 856 BranchSVNRev = None 857 if use_branch is None: 858 branchname, BranchSVNRev = get_branchinfo_from_hg(hg_parent) 859 use_branch = branchname 860 861 if svn_base is None: 862 svn_base = svn_base_of_branch(use_branch) 863 864 if not (BranchSVNRev is None): 865 ui.status("hg:%s alredy synced at svn.%s"%(hg_parent, BranchSVNRev)) 866 svn_switch_to(repos_url +svn_base+"@%s"%BranchSVNRev, ignore_ancetry=True, clean=True) 867 return 0 868 869 ui.status("need push of branch hg:%s rev %s - svn:%s" % (use_branch, hg_parent, svn_base )) 870 current_branch = use_branch 871 branch_switched = True 872 #first switch svn to desired branch. after that, switch hg to ones - this provides clean hg state 873 # that is requred by real_main 874 try: 875 svn_switch_to(repos_url +svn_base, ignore_ancetry=True) #clean=True 876 except (RunCommandError, ExternalCommandFailed) as e: 877 if (isinstance(e, RunCommandError)): 878 ui.status("cmd:%s"%e.cmd_string, level = ui.ERROR) 879 ui.status("ret:%s"%e.returncode, level = ui.ERROR) 880 ui.status("err:%s"%e.err_msg, level = ui.ERROR) 881 ui.status("out:%s"%e.out_msg, level = ui.ERROR) 882 else: 883 ui.status("err:%s"%str(e), level = ui.ERROR) 884 if e.err_have("E200009") or e.err_have("E160013") or e.err_have("path not found") or e.err_have("does not appear to be a URL"): 885 if (not options.auto_branch): 886 ui.status("looks that branch %s absent, so try to create it and start, and run again"%svn_base, level = ui.ERROR) 887 raise e 888 if autostart_branch(use_branch, options) < 0: 889 raise HgSVNError("cant autostart branch %s"%use_branch) 890 else: 891 raise e 892 except Exception as e: 893 ui.status("err uncknown:%s"%str(e), level = ui.ERROR) 894 raise e 895 896 ui.status("hg switch follow svn") 897 try: 898 if not hg_force_switch_branch(use_branch, strip_hg_rev(hg_parent) ): 899 if not options.force: 900 raise HgSVNError("failed to switch to branch <%s> for push" % use_branch) 901 use_options = copy.deepcopy(options) 902 use_options.svn_branch = use_branch 903 use_options.tip_rev = hg_parent 904 use_options.prefere2hg = True 905 res = real_main(use_options, [], clean=False ) 906 except Exception as e: 907 ui.status("err uncknown:%s"%str(e), level = ui.ERROR) 908 raise e 909 return res 910 911S_OK = 0 912ERR_HGSTATUS_BAD = 1 913ERR_ANONIMOUS_BRACHES = 2 914ERR_MERGE_FAIL = 3 915ERR_MERGE_DISABLED = 4 916ERR_NOT_OK_SOME = 5 917ERR_BRANCH_AUTOSTART = 6 918ERR_SYNC_LOST = 7 919 920def real_main(options, args, clean = True): 921 global repos_url 922 923 ui.status("start push branch %s to @%s"%(options.svn_branch, options.tip_rev), level=ui.DEBUG) 924 if run_hg(["st", "-S", "-m"]): 925 ui.status("There are uncommitted changes possibly in subrepos. Either commit them or put " 926 "them aside before running hgpushsvn.") 927 return ERR_HGSTATUS_BAD 928 if check_for_applied_patches(): 929 ui.status("There are applied mq patches. Put them aside before running hgpushsvn.") 930 return ERR_HGSTATUS_BAD 931 932 # through_hgrev preserves rev of hystory tree through that will push. for option prefere2hg this will use current hg-revision 933 through_hgrev = None 934 orig_branch = run_hg(["branch"]).strip() 935 936 is_at_pull_point = False; 937 while True: 938 svn_info = get_svn_info(".") 939 # in last_changed_rev - rev of last modification of current branch! 940 svn_current_rev = svn_info["revision"] #last_changed_rev 941 # e.g. u'svn://svn.twistedmatrix.com/svn/Twisted' 942 repos_url = svn_info["repos_url"] 943 # e.g. u'svn://svn.twistedmatrix.com/svn/Twisted/branches/xmpp-subprotocols-2178-2' 944 wc_url = svn_info["url"] 945 assert wc_url.startswith(repos_url) 946 # e.g. u'/branches/xmpp-subprotocols-2178-2' 947 wc_base = wc_url[len(repos_url):] 948 949 svn_branch = wc_url.split("/")[-1] 950 951 svn_branch = check_branchmap(svn_branch, wc_base, options) 952 953 svn_base = svn_base_of_branch(orig_branch) 954 955 if is_at_pull_point: 956 break 957 is_at_pull_point = True; 958 959 if options.prefere2hg: # and not options.tip_rev 960 # Prepare and/or switch named branches 961 if hg_is_clean(orig_branch) or svn_is_clean(wc_base): 962 sync_point =get_svn_rev_of_hgbranch(orig_branch) 963 svn_syncrev = sync_point.svnrev 964 hg_syncrev = sync_point.hgrev 965 hg_ok = not (hg_syncrev is None) 966 svn_ok = not (svn_syncrev is None) 967 synck_ok = hg_ok and svn_ok 968 if synck_ok and same_hg_rev(hg_syncrev, options.tip_rev): 969 #this is lucky day 970 ui.status("requred hgrev:%s alredy synced to svn:%s .. done"%(hg_syncrev, svn_syncrev)) 971 return 0; 972 973 # through_hgrev preserves rev of hystory tree through that will push. for option prefere2hg this will use current hg-revision 974 #if (options.tip_rev is None) or (len(options.tip_rev) == 0): 975 through_hgrev = get_hg_current_cset() 976 977 if IsDebug(): 978 ui.status('at sync point:%s'%sync_point) 979 if orig_branch != svn_branch: 980 # Update to or create the "pristine" branch 981 if IsDebug(): 982 ui.status('differ branch from hg:%s svn:%s@%s'%(orig_branch, svn_branch, str(svn_syncrev)), level=ui.DEBUG) 983 if svn_ok: 984 ui.status("svn follow hg(%s-%s) to %s@%d",orig_branch, hg_syncrev, svn_base, svn_syncrev); 985 svn_switch_to(repos_url+svn_base, svn_syncrev, clean=False, ignore_ancetry=True, propagate_rev=True) 986 continue; 987 elif not hg_ok: 988 ui.status("cant find sync origin. last checked branch %s"%orig_branch, level=ui.ERROR); 989 return ERR_SYNC_LOST 990 elif (svn_current_rev != svn_syncrev) : 991 if IsDebug(): 992 ui.status('differ sync rev current:%s synched:%s'%(str(svn_current_rev), str(svn_syncrev)), level=ui.DEBUG) 993 if svn_ok: 994 args = ["up", "--ignore-externals"] 995 if get_svn_client_version() >= (1, 5): 996 args.extend(['--accept', 'working']) 997 ui.status('Attempting to update to last hg-synched revision %s...', str(svn_syncrev)) 998 run_svn(args + ["-r", svn_syncrev, '.']); 999 continue; 1000 elif not hg_ok: 1001 # look like this hg-branch is not pushed, so go svn to branch-parent point, and try to push from it 1002 ui.status("cant autostart branch %s, switch svn to it`s base and push from that"%orig_branch, level=ui.ERROR); 1003 return ERR_BRANCH_AUTOSTART 1004 else: 1005 break 1006 #here is because !svn_ok 1007 if not (sync_point.svn_base or sync_point.svn_src) is None: 1008 #possibly svn-branch started but not synced with hg 1009 if check_svn_branch_sync_origin(sync_point, repos_url+svn_base): 1010 svn_syncrev = sync_point.svnrev 1011 ui.status("svn follow hg(%s-%s) to %s@%d",orig_branch, hg_syncrev, svn_base, svn_syncrev); 1012 continue; 1013 ui.status("cant sync origin to svn. last checked branch %s"%orig_branch, level=ui.ERROR); 1014 return ERR_SYNC_LOST 1015 if hg_ok: 1016 from_hgrev = strip_hg_rev(hg_syncrev) 1017 if int(from_hgrev) > 0: 1018 res = hg_push_branch(options, hg_syncrev, orig_branch, svn_base) 1019 if res != 0: 1020 return res 1021 continue 1022 else: 1023 #from_hgrev <= 0 for empty repository 1024 if options.auto_import: 1025 if svn_branch_empty(repos_url+svn_base): 1026 ui.status("svn branch %s empty, so import repository here"%svn_base, level=ui.ERROR); 1027 svn_switch_to(repos_url+svn_base, "HEAD", clean=False, ignore_ancetry=True, propagate_rev=True) 1028 continue; 1029 else: 1030 ui.status("svn branch %s not empty, so can`t import"%svn_base, level=ui.ERROR); 1031 ui.status("repository is not imported to svn", level=ui.ERROR); 1032 return ERR_BRANCH_AUTOSTART 1033 else: 1034 ui.status("svn branch:%s not mutch prefered one hg:%s"%(svn_branch, orig_branch), level = ui.WARNING) 1035 break 1036 1037 # Get remote SVN info 1038 svn_greatest_rev = get_svn_info(wc_url)['last_changed_rev'] 1039 ui.status("push at svn rev%s (greatest rev%s)"%(svn_current_rev , svn_greatest_rev), level=ui.DEBUG) 1040 1041 if svn_current_rev > svn_greatest_rev : 1042 #up to date svn rev may be far away from last commited rev 1043 svn_current_rev = svn_greatest_rev 1044 1045 if svn_greatest_rev != svn_current_rev and not options.prefere2hg : 1046 # We can't go on if the pristine branch isn't up to date. 1047 # If the pristine branch lacks some revisions from SVN we are not 1048 # able to pull them afterwards. 1049 # For example, if the last SVN revision in out hgsvn repository is 1050 # r100 and the latest SVN revision is r101, hgpushsvn would create 1051 # a tag svn.102 on top of svn.100, but svn.101 isn't in hg history. 1052 ui.status("Branch '%s' out of date. Run 'hgpullsvn' first." % svn_branch) 1053 return ERR_HGSTATUS_BAD 1054 1055 # Switch branches if necessary. 1056 if orig_branch != svn_branch: 1057 if not hg_switch_branch(orig_branch, svn_branch): 1058 return ERR_HGSTATUS_BAD 1059 1060 hg_start_rev = "svn.%d" % svn_current_rev 1061 hg_revs = None 1062 hg_tree = None 1063 #proceed current branch till its head 1064 hg_tip = strip_hg_rev(options.tip_rev or "") 1065 try: 1066 hg_start_cset = get_hg_cset(hg_start_rev) 1067 except (RuntimeError, RunCommandError): 1068 if not options.auto_import or not svn_branch_empty(repos_url+svn_base): 1069 ui.status("no %s in hg!!! try to proceed from last sync point by switches -l or-f " 1070 " or try --autoimport if repository not imported yet"%hg_start_rev 1071 ) 1072 raise 1073 hg_start_cset = get_hg_cset("0") 1074 ui.status( "Warning: revision '%s' not found, forcing to first rev '%s'" % (hg_start_rev, hg_start_cset) ) 1075 1076 #hg_start_branch = get_branch_from_hg(hg_start_cset) 1077 if same_hg_rev(hg_start_cset, hg_tip): 1078 #this is lucky day 1079 ui.status("requred hgrev:%s alredy synced to svn:%s .. done"%(hg_start_cset, svn_current_rev)) 1080 return 0; 1081 1082 if not options.collapse: 1083 if (through_hgrev is None) or ( int(strip_hg_rev(hg_start_cset)) >= int(strip_hg_rev(through_hgrev)) ): 1084 hg_revs,hg_tree = get_hg_revs_with_graph(hg_start_cset, svn_branch, last_rev=hg_tip, opts = options) 1085 else: 1086 ui.status("use current hg rev %s as pass-though specifier\n", through_hgrev); 1087 if (len(hg_tip) == 0): 1088 through_rev = through_hgrev 1089 else: 1090 tip_rev = get_hg_cset(hg_tip) 1091 through_rev = tip_rev if ( int(strip_hg_rev(tip_rev)) < int(strip_hg_rev(through_hgrev)) ) else through_hgrev 1092 hg_revs,hg_tree = get_hg_revs_with_graph(hg_start_cset, svn_branch, last_rev=through_rev, opts = options) 1093 if len(hg_revs) > 0: 1094 if IsDebug(): 1095 ui.status("have processing revisions set until pass-through:%s" % hg_revs, level=ui.DEBUG) 1096 if same_hg_rev(through_rev, hg_revs[len(hg_revs)-1] ) : 1097 hg_revs_2,hg_tree_2 = get_hg_revs_with_graph(through_rev, svn_branch, last_rev=hg_tip, opts = options) 1098 if len(hg_revs_2) > 0: 1099 if IsDebug(): 1100 ui.status("appends processing revisions set:%s" % hg_revs_2, level=ui.DEBUG) 1101 if same_hg_rev(through_hgrev, hg_revs_2[0] ) : 1102 if len(hg_revs) > 0: 1103 del hg_tree_2[hg_revs_2[0]] 1104 del hg_revs_2[0] 1105 hg_revs = hg_revs + hg_revs_2 1106 hg_tree.update(hg_tree_2) 1107 #raise HgSVNError("pass-though detected on hg:%s " % through_hgrev) 1108 1109 if len(hg_revs) <= 0: 1110 ui.status("looks like hg_tip:%s in another branch, and nothing to push in current branch:%s\n", hg_tip, svn_branch); 1111 return S_OK 1112 1113 # if branch started, branch head not enlisted in hg_revs, 1114 # thus use hg_start_cset as start from wich 1st branch revision borns 1115 hg_startid = strip_hg_revid(hg_start_cset) 1116 def check_synched(hg_startid): 1117 if ( get_svn_rev_from_hg(hg_startid) is None) : 1118 ui.status("start rev %s not synched" %(hg_startid)); 1119 return False 1120 return True 1121 def check_havestart(hg_startid): 1122 if (hg_startid not in hg_revs) : 1123 if IsDebug(): 1124 print_( "start rev %s not in push set %s" %(hg_startid, hg_revs) ) 1125 return False 1126 return True 1127 if (not check_havestart(hg_startid)) or (not check_synched(hg_startid)): 1128 hg_startrev = strip_hg_rev(hg_start_cset) 1129 start_parents = None 1130 if (int(hg_startrev) != 0): 1131 start_parents = get_parents_from_hg(hg_revs[0]) 1132 elif options.auto_import: 1133 start_parents = [hg_start_cset] 1134 if IsDebug(): 1135 print_( "start rev parent %s expects at start point %s" %(start_parents, hg_start_cset) ) 1136 if ([hg_start_cset] == start_parents) or options.force: 1137 # hgbranch starts right from svn_curent, so can commit branch head 1138 hg_revs = [strip_hg_revid(hg_start_cset)] + hg_revs; 1139 else: 1140 raise HgSVNError("head of branch <%s> is not in svn yet, have to push ones head-branch first" % svn_branch) 1141 1142 if hg_revs is None: 1143 hg_revs = [strip_hg_rev(hg_start_cset), strip_hg_rev(get_hg_cset(hg_tip))] 1144 1145 1146 pushed_svn_revs = [] 1147 allok = True; 1148 try: 1149 if options.dryrun: 1150 ui.status( "Outgoing revisions that would be pushed to SVN:" ) 1151 try: 1152 if IsDebug(): 1153 ui.status("processing revisions set:%s" % hg_revs, level=ui.DEBUG) 1154 last_commited_rev = -1 1155 svn_rev = None 1156 svn_switch_to(wc_url, 'HEAD', clean=False, ignore_ancetry=True, propagate_rev=True) 1157 #run_svn(['up', '--non-interactive', '--accept=working', '--ignore-externals', '-q']) 1158 for (prev_rev, next_rev) in get_pairs(hg_revs): 1159 ui.status("Committing changes up to revision %s", get_hg_cset(next_rev)) 1160 if not options.dryrun: 1161 #if not options.edit: 1162 # ??? 1163 username = options.username 1164 if options.keep_author: 1165 username = run_hg(["log", "-r", next_rev, "--template", "{author}"]) 1166 if is_anonimous_branch_head(prev_rev, hg_tree): 1167 ui.status("revision %s is a switch point for multiple anonimous branches" 1168 ", cant distingish wich way to up" % prev_rev) 1169 return ERR_ANONIMOUS_BRACHES 1170 1171 svn_merged = False 1172 if next_rev in hg_tree: 1173 if IsDebug(): 1174 print_( "target rev %s have parents %s" %(next_rev, hg_tree[next_rev].parents) ) 1175 if len(hg_tree[next_rev].parents)>1: 1176 ui.status("revision %s is a merge point" % next_rev) 1177 if (options.merge_branches == "break") or (options.merge_branches == ""): 1178 ui.status("break due to merging not enabled", level=ui.DEFAULT) 1179 return ERR_MERGE_DISABLED 1180 if options.merge_branches == "skip": 1181 ui.status("skip branches of merge") 1182 else: 1183 if hg_sync_merge_svn(prev_rev, next_rev, hg_tree, svn_branch, options=options): 1184 svn_merged = True 1185 ui.status("ok: revision %s merges fine" % next_rev) 1186 else: 1187 if options.merge_branches != "trypush": 1188 return ERR_MERGE_FAIL 1189 ui.status("failed to branches of merge, so normal commit") 1190 1191 svn_rev = hg_push_svn(prev_rev, next_rev, 1192 edit=options.edit, 1193 username=username, 1194 password=options.password, 1195 cache=options.cache, 1196 options=options 1197 , use_svn_wc = svn_merged 1198 ) 1199 if svn_rev: 1200 if svn_rev >= 0: 1201 # Issue 95 - added update to prevent add/modify/delete crash 1202 run_svn(["up", "--ignore-externals"]) 1203 map_svn_rev_to_hg(svn_rev, next_rev, local=True) 1204 pushed_svn_revs.append(svn_rev) 1205 last_commited_rev = svn_rev 1206 #if prev_rev == hg_revs[0]: 1207 hg_tag_svn_head(branch=svn_branch) 1208 #activate this head bookmark will not take effect cause we always use update -r rev 1209 #run_hg(["update", "-r", svn_branch]) 1210 else: 1211 ui.status( run_hg(["log", "-r", next_rev, 1212 "--template", "{rev}:{node|short} {desc}"]) ) 1213 if svn_rev: 1214 if (svn_rev < 0) and (last_commited_rev > 0): 1215 # Issue 89 - empty changesets are should be market by svn tag or else 1216 # witout one it cant by idetnifyed for branch merge parent 1217 map_svn_rev_to_hg(last_commited_rev, next_rev, local=True, force=True) 1218 except (RunCommandError) as e: 1219 ui.status( "cmd:%s"%(e.cmd_string), level = ui.ERROR ) 1220 ui.status( "ret:%s"%(e.returncode), level = ui.ERROR ) 1221 ui.status( "err:%s"%(e.err_msg), level = ui.ERROR ) 1222 ui.status( "out:%S"%(e.out_msg), level = ui.ERROR ) 1223 run_hg(["revert", "--all"]) 1224 raise 1225 1226 except Exception as e: 1227 ui.status("exception was:%s"%e, level = ui.ERROR) 1228 print_(traceback.format_exc()) 1229 # TODO: Add --no-backup to leave a "clean" repo behind if something 1230 # fails? 1231 run_hg(["revert", "--all"]) 1232 allok = False 1233 1234 except Exception as e: 1235 ui.status("exception was:%s"%e, level = ui.ERROR) 1236 print_(traceback.format_exc()) 1237 allok = False 1238 1239 if last_commited_rev > 0: 1240 hg_refresh_svn_head( svn_rev = last_commited_rev ) 1241 hg_release_svn_head() 1242 1243 if clean and not IsDebug(): 1244 work_branch = orig_branch 1245 if work_branch != svn_branch: 1246 run_hg(["up", "-C", work_branch]) 1247 run_hg(["branch", work_branch]) 1248 1249 if not allok: 1250 return ERR_NOT_OK_SOME 1251 1252 if pushed_svn_revs: 1253 if len(pushed_svn_revs) == 1: 1254 msg = "Pushed one revision to SVN: " 1255 else: 1256 msg = "Pushed %d revisions to SVN: " % len(pushed_svn_revs) 1257 run_svn(["up", "-r", pushed_svn_revs[-1]]) 1258 ui.status("%s %s", msg, ", ".join(str(x) for x in pushed_svn_revs)) 1259 try: 1260 for line in run_hg(["st"]).splitlines(): 1261 if line.startswith("M"): 1262 ui.status(("Mercurial repository has local changes after " 1263 "SVN update.")) 1264 ui.status(("This may happen with SVN keyword expansions.")) 1265 break 1266 except: 1267 ui.status("Cant check repository status") 1268 1269 elif not options.dryrun: 1270 ui.status("Nothing to do.") 1271 1272 return S_OK 1273 1274def on_option_svnignore_add(option, opt, value, parser): 1275 if (opt == "--svnignore-use"): 1276 append_ignore4svn(value) 1277 elif (opt == "--svnignore-save"): 1278 save_svnignores() 1279 1280def main(argv=sys.argv): 1281 # Defined as entry point. Must be callable without arguments. 1282 usage = "usage: %prog [-cf]" 1283 parser = OptionParser(usage) 1284 parser.add_option("-f", "--force", default=False, action="store_true", 1285 dest="force", 1286 help="push even if no hg tag found for current SVN rev.") 1287 parser.add_option("-c", "--collapse", default=False, action="store_true", 1288 dest="collapse", 1289 help="collapse all hg changesets in a single SVN commit") 1290 parser.add_option("-r", type="string", dest="tip_rev", default=None, 1291 help="limit push up to specified revision") 1292 parser.add_option("--branch", type="string", dest="svn_branch", 1293 help="override branch name (defaults to last path component of <SVN URL>)") 1294 parser.add_option("-n", "--dry-run", default=False, action="store_true", 1295 dest="dryrun", 1296 help="show outgoing changes to SVN without pushing them") 1297 parser.add_option("-e", "--edit", default=False, action="store_true", 1298 dest="edit", 1299 help="edit commit message using external editor") 1300 parser.add_option("-a", "--noversioned_change", type="string", default="abort", 1301 dest="on_noversioned_change", 1302 help="=<add> - add to svn repo files that noverioned, <skip> - ignore this changes") 1303 parser.add_option("--merge-branches", default="break", type="string", 1304 dest="merge_branches", 1305 help=("<push> - try to push named branches at merge point," 1306 "registered in branches map " 1307 "\n<skip> - commit as ordinary revision " 1308 "\n<trypush> - if cant push branches, commit as ordinary revision" 1309 "\n<>|<break> - default:do not merge, and break pushing" 1310 ) 1311 ) 1312 parser.add_option("-u", "--username", default=None, action="store", type="string", 1313 dest="username", 1314 help="specify a username ARG as same as svn --username") 1315 parser.add_option("-p", "--password", default=None, action="store", type="string", 1316 dest="password", 1317 help="specify a password ARG as same as svn --password") 1318 parser.add_option("--no-auth-cache", default=False, action="store_true", 1319 dest="cache", 1320 help="Prevents caching of authentication information") 1321 parser.add_option("--keep-author", default=False, action="store_true", 1322 dest="keep_author", 1323 help="keep the author when committing to SVN") 1324 parser.add_option("--svnignore-use", type="string", action="callback", callback=on_option_svnignore_add, 1325 help="ignore hg-versioned file from committing to SVN") 1326 parser.add_option("--svnignore-save", action="callback", callback=on_option_svnignore_add, 1327 help=("save svnignores permanently: - add it to .svnignore list") 1328 ) 1329 parser.add_option("--svn-accept", type="string", default="working", dest="svnacception", 1330 help=("defines avn accept options for merge cmd") 1331 ) 1332 parser.add_option("--autoimport", default=False, action="store_true", dest="auto_import", 1333 help=("allow import repository at detected svn branch root" 1334 " (branch nust be empty!!!)" 1335 ) 1336 ) 1337 parser.add_option("--auto-start-branch", default=False, action="store_true", dest="auto_branch", 1338 help=("allow create mapped branch at svn branch root if one absent") 1339 ) 1340 load_svnignores() 1341 load_hgsvn_branches_map() 1342 (options, args) = run_parser(parser, __doc__, argv) 1343 if args: 1344 display_parser_error(parser, "incorrect number of arguments") 1345 return locked_main(real_main, options, args) 1346 1347if __name__ == "__main__": 1348 sys.exit(main() or 0) 1349 1350