def _run_raw_shell_command(cmd): ui.status("* %s", cmd, level=ui.DEBUG) st, out = commands.getstatusoutput(cmd) if st != 0: raise ExternalCommandFailed( "External program failed with non-zero return code (%d): %s\n%s" % (st, cmd, out)) return out
def hg_is_clean(current_branch): """Returns False if the local Mercurial repository has uncommitted changes.""" if run_hg(['st', '-mard'], output_is_locale_encoding=True).strip(): ui.status(("\nThe Mercurial working copy contains pending changes " "in branch '%s'!\n" "Please either commit or discard them before running " "'%s' again." % (current_branch, get_script_name())), level=ui.ERROR) return False return True
def strip_nested_removes(targets): """Strip files within removed folders and return a cleaned up list.""" # We're going a safe way here: "svn info" fails on items within removed # dirs. clean_targets = [] for target in targets: try: run_svn(['info', '--xml', target], mask_atsign=True) except ExternalCommandFailed, err: ui.status(str(err), level=ui.DEBUG) continue clean_targets.append(target)
def once_or_more(desc, retry, function, *args, **kwargs): """Try executing the provided function at least once. If ``retry`` is ``True``, running the function with the given arguments if an ``Exception`` is raised. Otherwise, only run the function once. The string ``desc`` should be set to a short description of the operation. """ while True: try: return function(*args, **kwargs) except Exception, e: ui.status('%s failed: %s', desc, str(e)) if retry: ui.status('Trying %s again...', desc) continue else: raise
def _run_raw_command(cmd, args, fail_if_stderr=False): cmd_string = "%s %s" % (cmd, " ".join(map(shell_quote, args))) ui.status("* %s", cmd_string, level=ui.DEBUG) try: pipe = Popen([cmd] + args, executable=cmd, stdout=PIPE, stderr=PIPE) except OSError: etype, value = sys.exc_info()[:2] raise ExternalCommandFailed( "Failed running external program: %s\nError: %s" % (cmd_string, "".join(traceback.format_exception_only(etype, value)))) out, err = pipe.communicate() if "nothing changed" == out.strip(): # skip this error return out if pipe.returncode != 0 or (fail_if_stderr and err.strip()): raise ExternalCommandFailed( "External program failed (return code %d): %s\n%s\n%s" % (pipe.returncode, cmd_string, err, out)) return out
def iter_svn_log_entries(svn_url, first_rev, last_rev, retry): """ Iterate over SVN log entries between first_rev and last_rev. This function features chunked log fetching so that it isn't too nasty to the SVN server if many entries are requested. """ cur_rev = first_rev chunk_length = log_min_chunk_length first_run = True while last_rev == "HEAD" or cur_rev <= last_rev: start_t = time.time() stop_rev = min(last_rev, cur_rev + chunk_length) ui.status("Fetching %s SVN log entries starting from revision %d...", chunk_length, cur_rev, level=ui.VERBOSE) entries = once_or_more("Fetching SVN log", retry, run_svn_log, svn_url, cur_rev, "HEAD", chunk_length) duration = time.time() - start_t if not first_run: # skip first revision on subsequent runs, as it is overlapped entries.pop(0) first_run = False if not entries: break for e in entries: if e['revision'] > last_rev: break yield e if e['revision'] >= last_rev: break cur_rev = e['revision'] # Adapt chunk length based on measured request duration if duration < log_duration_threshold: chunk_length = int(chunk_length * 2.0) elif duration > log_duration_threshold * 2: chunk_length = max(log_min_chunk_length, int(chunk_length / 2.0))
def pull_svn_rev(log_entry, current_rev, svn_wc, wc_url, wc_base, retry): """ Pull SVN changes from the given log entry. Returns the new SVN revision. If an exception occurs, it will rollback to revision 'current_rev'. """ svn_rev = log_entry['revision'] added_paths = [] copied_paths = [] removed_paths = [] changed_paths = [] unrelated_paths = [] replaced_paths = {} # 1. Prepare for the `svn up` changes that are pulled in the second step # by analyzing log_entry for the changeset for d in log_entry['changed_paths']: # e.g. u'/branches/xmpp-subprotocols-2178-2/twisted/words/test/test_jabberxmlstream.py' p = d['path'] if not p.startswith(wc_base + "/"): # Ignore changed files that are not part of this subdir if p != wc_base: unrelated_paths.append(p) continue action = d['action'] if action not in 'MARD': raise UnsupportedSVNAction( "In SVN rev. %d: action '%s' not supported. Please report a bug!" % (svn_rev, action)) # e.g. u'twisted/words/test/test_jabberxmlstream.py' p = p[len(wc_base):].strip("/") # Record for commit changed_paths.append(p) # Detect special cases old_p = d['copyfrom_path'] if old_p and old_p.startswith(wc_base + "/"): old_p = old_p[len(wc_base):].strip("/") # Both paths can be identical if copied from an old rev. # We treat like it a normal change. if old_p != p: # Try to hint hg about file and dir copies if not os.path.isdir(old_p): copied_paths.append((old_p, p)) if action == 'R': removed_paths.append(old_p) else: # Extract actual copied files (hg doesn't track dirs # and will refuse "hg copy -A" with dirs) r = run_hg(["st", "-nc"], [old_p], output_is_locale_encoding=True) for old_f in r.splitlines(): f = p + old_f[len(old_p):] copied_paths.append((old_f, f)) if action == 'R': removed_paths.append(old_f) continue if d['action'] == 'A': added_paths.append(p) elif d['action'] == 'D': # Same as above: unfold directories into their affected files if not os.path.isdir(p): removed_paths.append(p) else: r = run_hg(["st", "-nc"], [p], output_is_locale_encoding=True) for f in r.splitlines(): removed_paths.append(f) elif d['action'] == 'R': # (R)eplaced directories can have added and removed files without # them being mentioned in the SVN log => we must detect those files # ourselves. # (http://svn.python.org/projects/python/branches/py3k, rev 59625) if os.path.isdir(p): replaced_paths[p] = get_svn_versioned_files( os.path.join(svn_wc, p)) else: # We never know what twisty semantics (R) can have. addremove # is safest. added_paths.append(p) # 2. Update SVN + add/remove/commit hg try: if changed_paths: args = ["up", "--ignore-externals"] if get_svn_client_version() >= (1, 5): args.extend(['--accept', 'postpone']) ui.status('Attempting to update to revision %s...', str(svn_rev)) once_or_more("SVN update", retry, run_svn, args + ["-r", svn_rev, svn_wc]) conflicts = [] for status_entry in get_svn_status('.'): if status_entry['status'] == 'conflicted': conflicts.append(status_entry['path']) if conflicts: ui.status('SVN updated resulted in conflicts!', level=ui.ERROR) ui.status('Conflicted files: %s', ','.join(conflicts)) ui.status('Please report a bug.') return None for p, old_contents in replaced_paths.items(): old_contents = set(old_contents) new_contents = set( get_svn_versioned_files(os.path.join(svn_wc, p))) added_paths.extend(p + '/' + f for f in new_contents - old_contents) removed_paths.extend(p + '/' + f for f in old_contents - new_contents) if added_paths: # Use 'addremove' because an added SVN directory may # overwrite a previous directory with the same name. # XXX what about untracked files in those directories? run_hg(["addremove"] + hg_exclude_options, added_paths) for old, new in copied_paths: try: run_hg(["copy", "-A"], [old, new]) except ExternalCommandFailed: # Maybe the "old" path is obsolete, i.e. it comes from an # old SVN revision and was later removed. s = run_hg(['st', '-nd'], [old], output_is_locale_encoding=True) if s.strip(): # The old path is known by hg, something else happened. raise run_hg(["add"], [new]) if removed_paths: try: for file_path in removed_paths: if os.path.exists(file_path): run_hg(["remove", "-A"], file_path) except (ExternalCommandFailed), e: if str(e).find("file is untracked") > 0: ui.status( "Ignoring warnings about untracked files: '%s'" % str(e), level=ui.VERBOSE) else: raise hg_commit_from_svn_log_entry(log_entry) elif unrelated_paths: detect_overwritten_svn_branch(wc_url, svn_rev)
def real_main(options, args): if check_for_applied_patches(): print("There are applied mq patches. Put them aside before " "running hgpullsvn.") return 1 svn_wc = "." # Get SVN info svn_info = get_svn_info('.') current_rev = svn_info['revision'] next_rev = current_rev + 1 # e.g. u'svn://svn.twistedmatrix.com/svn/Twisted' repos_url = svn_info['repos_url'] # e.g. u'svn://svn.twistedmatrix.com/svn/Twisted/branches/xmpp-subprotocols-2178-2' wc_url = svn_info['url'] assert wc_url.startswith(repos_url) # e.g. u'/branches/xmpp-subprotocols-2178-2' wc_base = wc_url[len(repos_url):] # e.g. 'xmpp-subprotocols-2178-2' svn_branch = wc_url.split("/")[-1] # if --branch was passed, override the branch name derived above if options.svn_branch: svn_branch = options.svn_branch if options.svn_peg: wc_url += "@" + str(options.svn_peg) # Get remote SVN info ui.status("Retrieving remote SVN info...", level=ui.VERBOSE) svn_greatest_rev = get_svn_info(wc_url)['last_changed_rev'] if svn_greatest_rev < next_rev: ui.status("No revisions after %s in SVN repo, nothing to do", svn_greatest_rev) return elif options.svn_rev != None: if options.svn_rev < next_rev: ui.status( "All revisions up to %s are already pulled in, " "nothing to do", options.svn_rev) return svn_greatest_rev = options.svn_rev # Show incoming changes if in dry-run mode. if options.dryrun: ui.status("Incoming SVN revisions:") for entry in iter_svn_log_entries(wc_url, next_rev, svn_greatest_rev, options.svnretry): if entry["message"]: msg = entry["message"].splitlines()[0].strip() else: msg = "" line = "[%d] %s (%s)" % (entry["revision"], msg, entry["author"]) ui.status(line) return # Prepare and/or switch named branches orig_branch = run_hg(["branch"]).strip() if orig_branch != svn_branch: # Update to or create the "pristine" branch if not hg_switch_branch(orig_branch, svn_branch): return 1 elif not hg_is_clean(svn_branch): return 1 # Detect that a previously aborted hgpullsvn retrieved an SVN revision # without committing it to hg. # If there is no SVN tag in current head, we may have been interrupted # during the previous "hg tag". hgsvn_rev = get_svn_rev_from_hg() if hgsvn_rev is not None and hgsvn_rev != current_rev: ui.status(("\nNote: hgsvn repository is in an unclean state " "(probably because of an aborted hgpullsvn). \n" "Let's first update to the last good SVN rev."), level=ui.VERBOSE) run_svn(["revert", "-R", "."]) run_svn(["up", "--ignore-externals", "-r", hgsvn_rev, svn_wc]) next_rev = hgsvn_rev + 1 # Reset working branch to last svn head to have a clean and linear SVN # history. heads_before = None if hgsvn_rev is None: heads_before = run_hg(["heads", "--template", "{node}%s" % os.linesep]).splitlines() run_hg(["update", "-C", "svn.%d" % current_rev]) # Load SVN log starting from current rev it_log_entries = iter_svn_log_entries(wc_url, next_rev, svn_greatest_rev, options.svnretry) try: try: for log_entry in it_log_entries: current_rev = pull_svn_rev(log_entry, current_rev, svn_wc, wc_url, wc_base, options.svnretry) if current_rev is None: return 1 ui.status("Pulled r%d %s (%s)", log_entry["revision"], log_entry["message"], log_entry["author"]) # TODO: detect externals with "svn status" and update them as well finally: if heads_before is not None: # we have reset the SVN branch heads_now = run_hg( ["heads", "--template", "{node}%s" % os.linesep]).splitlines() if len(heads_now) != len(heads_before): ui.status("created new head in branch '%s'", svn_branch) work_branch = orig_branch or svn_branch if work_branch != svn_branch: run_hg(["up", '-C', work_branch]) run_hg(["branch", work_branch]) except KeyboardInterrupt: ui.status("\nStopped by user.", level=ui.ERROR) except ExternalCommandFailed, e: ui.status(str(e), level=ui.ERROR)
if os.path.exists(file_path): run_hg(["remove", "-A"], file_path) except (ExternalCommandFailed), e: if str(e).find("file is untracked") > 0: ui.status( "Ignoring warnings about untracked files: '%s'" % str(e), level=ui.VERBOSE) else: raise hg_commit_from_svn_log_entry(log_entry) elif unrelated_paths: detect_overwritten_svn_branch(wc_url, svn_rev) # NOTE: in Python 2.5, KeyboardInterrupt isn't a subclass of Exception anymore except (Exception, KeyboardInterrupt), e: ui.status("\nInterrupted, please wait for cleanup!\n", level=ui.ERROR) # NOTE: at this point, running SVN sometimes gives "write lock failures" # which leave the WC in a weird state. time.sleep(0.3) run_svn(["cleanup"]) hgsvn_rev = get_svn_rev_from_hg() if hgsvn_rev != svn_rev: # Unless the tag is there, revert to the last stable state run_svn(["up", "--ignore-externals", "-r", current_rev, svn_wc]) run_hg(["revert", "--all"]) raise return svn_rev def real_main(options, args):
def pull_svn_rev(log_entry, current_rev, svn_wc, wc_url, wc_base, retry): """ Pull SVN changes from the given log entry. Returns the new SVN revision. If an exception occurs, it will rollback to revision 'current_rev'. """ svn_rev = log_entry['revision'] added_paths = [] copied_paths = [] removed_paths = [] changed_paths = [] unrelated_paths = [] replaced_paths = {} # 1. Prepare for the `svn up` changes that are pulled in the second step # by analyzing log_entry for the changeset for d in log_entry['changed_paths']: # e.g. u'/branches/xmpp-subprotocols-2178-2/twisted/words/test/test_jabberxmlstream.py' p = d['path'] if not p.startswith(wc_base + "/"): # Ignore changed files that are not part of this subdir if p != wc_base: unrelated_paths.append(p) continue action = d['action'] if action not in 'MARD': raise UnsupportedSVNAction("In SVN rev. %d: action '%s' not supported. Please report a bug!" % (svn_rev, action)) # e.g. u'twisted/words/test/test_jabberxmlstream.py' p = p[len(wc_base):].strip("/") # Record for commit changed_paths.append(p) # Detect special cases old_p = d['copyfrom_path'] if old_p and old_p.startswith(wc_base + "/"): old_p = old_p[len(wc_base):].strip("/") # Both paths can be identical if copied from an old rev. # We treat like it a normal change. if old_p != p: # Try to hint hg about file and dir copies if not os.path.isdir(old_p): copied_paths.append((old_p, p)) if action == 'R': removed_paths.append(old_p) else: # Extract actual copied files (hg doesn't track dirs # and will refuse "hg copy -A" with dirs) r = run_hg(["st", "-nc"], [old_p], output_is_locale_encoding=True) for old_f in r.splitlines(): f = p + old_f[len(old_p):] copied_paths.append((old_f, f)) if action == 'R': removed_paths.append(old_f) continue if d['action'] == 'A': added_paths.append(p) elif d['action'] == 'D': # Same as above: unfold directories into their affected files if not os.path.isdir(p): removed_paths.append(p) else: r = run_hg(["st", "-nc"], [p], output_is_locale_encoding=True) for f in r.splitlines(): removed_paths.append(f) elif d['action'] == 'R': # (R)eplaced directories can have added and removed files without # them being mentioned in the SVN log => we must detect those files # ourselves. # (http://svn.python.org/projects/python/branches/py3k, rev 59625) if os.path.isdir(p): replaced_paths[p] = get_svn_versioned_files( os.path.join(svn_wc, p)) else: # We never know what twisty semantics (R) can have. addremove # is safest. added_paths.append(p) # 2. Update SVN + add/remove/commit hg try: if changed_paths: args = ["up", "--ignore-externals"] if get_svn_client_version() >= (1, 5): args.extend(['--accept', 'postpone']) ui.status('Attempting to update to revision %s...', str(svn_rev)) once_or_more("SVN update", retry, run_svn, args + ["-r", svn_rev, svn_wc]) conflicts = [] for status_entry in get_svn_status('.'): if status_entry['status'] == 'conflicted': conflicts.append(status_entry['path']) if conflicts: ui.status('SVN updated resulted in conflicts!', level=ui.ERROR) ui.status('Conflicted files: %s', ','.join(conflicts)) ui.status('Please report a bug.') return None for p, old_contents in replaced_paths.items(): old_contents = set(old_contents) new_contents = set(get_svn_versioned_files( os.path.join(svn_wc, p))) added_paths.extend(p + '/' + f for f in new_contents - old_contents) removed_paths.extend(p + '/' + f for f in old_contents - new_contents) if added_paths: # Use 'addremove' because an added SVN directory may # overwrite a previous directory with the same name. # XXX what about untracked files in those directories? run_hg(["addremove"] + hg_exclude_options, added_paths) for old, new in copied_paths: try: run_hg(["copy", "-A"], [old, new]) except ExternalCommandFailed: # Maybe the "old" path is obsolete, i.e. it comes from an # old SVN revision and was later removed. s = run_hg(['st', '-nd'], [old], output_is_locale_encoding=True) if s.strip(): # The old path is known by hg, something else happened. raise run_hg(["add"], [new]) if removed_paths: try: for file_path in removed_paths: if os.path.exists(file_path): run_hg(["remove", "-A"], file_path) except (ExternalCommandFailed), e: if str(e).find("file is untracked") > 0: ui.status("Ignoring warnings about untracked files: '%s'" % str(e), level=ui.VERBOSE) else: raise hg_commit_from_svn_log_entry(log_entry) elif unrelated_paths: detect_overwritten_svn_branch(wc_url, svn_rev)
def real_main(options, args): if check_for_applied_patches(): print ("There are applied mq patches. Put them aside before " "running hgpullsvn.") return 1 svn_wc = "." # Get SVN info svn_info = get_svn_info('.') current_rev = svn_info['revision'] next_rev = current_rev + 1 # e.g. u'svn://svn.twistedmatrix.com/svn/Twisted' repos_url = svn_info['repos_url'] # e.g. u'svn://svn.twistedmatrix.com/svn/Twisted/branches/xmpp-subprotocols-2178-2' wc_url = svn_info['url'] assert wc_url.startswith(repos_url) # e.g. u'/branches/xmpp-subprotocols-2178-2' wc_base = wc_url[len(repos_url):] # e.g. 'xmpp-subprotocols-2178-2' svn_branch = wc_url.split("/")[-1] # if --branch was passed, override the branch name derived above if options.svn_branch: svn_branch = options.svn_branch if options.svn_peg: wc_url += "@" + str(options.svn_peg) # Get remote SVN info ui.status("Retrieving remote SVN info...", level=ui.VERBOSE) svn_greatest_rev = get_svn_info(wc_url)['last_changed_rev'] if svn_greatest_rev < next_rev: ui.status("No revisions after %s in SVN repo, nothing to do", svn_greatest_rev) return elif options.svn_rev != None: if options.svn_rev < next_rev: ui.status("All revisions up to %s are already pulled in, " "nothing to do", options.svn_rev) return svn_greatest_rev = options.svn_rev # Show incoming changes if in dry-run mode. if options.dryrun: ui.status("Incoming SVN revisions:") for entry in iter_svn_log_entries(wc_url, next_rev, svn_greatest_rev, options.svnretry): if entry["message"]: msg = entry["message"].splitlines()[0].strip() else: msg = "" line = "[%d] %s (%s)" % (entry["revision"], msg, entry["author"]) ui.status(line) return # Prepare and/or switch named branches orig_branch = run_hg(["branch"]).strip() if orig_branch != svn_branch: # Update to or create the "pristine" branch if not hg_switch_branch(orig_branch, svn_branch): return 1 elif not hg_is_clean(svn_branch): return 1 # Detect that a previously aborted hgpullsvn retrieved an SVN revision # without committing it to hg. # If there is no SVN tag in current head, we may have been interrupted # during the previous "hg tag". hgsvn_rev = get_svn_rev_from_hg() if hgsvn_rev is not None and hgsvn_rev != current_rev: ui.status(("\nNote: hgsvn repository is in an unclean state " "(probably because of an aborted hgpullsvn). \n" "Let's first update to the last good SVN rev."), level=ui.VERBOSE) run_svn(["revert", "-R", "."]) run_svn(["up", "--ignore-externals", "-r", hgsvn_rev, svn_wc]) next_rev = hgsvn_rev + 1 # Reset working branch to last svn head to have a clean and linear SVN # history. heads_before = None if hgsvn_rev is None: heads_before = run_hg(["heads", "--template", "{node}%s" % os.linesep]).splitlines() run_hg(["update", "-C", "svn.%d" % current_rev]) # Load SVN log starting from current rev it_log_entries = iter_svn_log_entries(wc_url, next_rev, svn_greatest_rev, options.svnretry) try: try: for log_entry in it_log_entries: current_rev = pull_svn_rev(log_entry, current_rev, svn_wc, wc_url, wc_base, options.svnretry) if current_rev is None: return 1 ui.status("Pulled r%d %s (%s)", log_entry["revision"], log_entry["message"], log_entry["author"]) # TODO: detect externals with "svn status" and update them as well finally: if heads_before is not None: # we have reset the SVN branch heads_now = run_hg(["heads", "--template", "{node}%s" % os.linesep]).splitlines() if len(heads_now) != len(heads_before): ui.status("created new head in branch '%s'", svn_branch) work_branch = orig_branch or svn_branch if work_branch != svn_branch: run_hg(["up", '-C', work_branch]) run_hg(["branch", work_branch]) except KeyboardInterrupt: ui.status("\nStopped by user.", level=ui.ERROR) except ExternalCommandFailed, e: ui.status(str(e), level=ui.ERROR)
if removed_paths: try: for file_path in removed_paths: if os.path.exists(file_path): run_hg(["remove", "-A"], file_path) except (ExternalCommandFailed), e: if str(e).find("file is untracked") > 0: ui.status("Ignoring warnings about untracked files: '%s'" % str(e), level=ui.VERBOSE) else: raise hg_commit_from_svn_log_entry(log_entry) elif unrelated_paths: detect_overwritten_svn_branch(wc_url, svn_rev) # NOTE: in Python 2.5, KeyboardInterrupt isn't a subclass of Exception anymore except (Exception, KeyboardInterrupt), e: ui.status("\nInterrupted, please wait for cleanup!\n", level=ui.ERROR) # NOTE: at this point, running SVN sometimes gives "write lock failures" # which leave the WC in a weird state. time.sleep(0.3) run_svn(["cleanup"]) hgsvn_rev = get_svn_rev_from_hg() if hgsvn_rev != svn_rev: # Unless the tag is there, revert to the last stable state run_svn(["up", "--ignore-externals", "-r", current_rev, svn_wc]) run_hg(["revert", "--all"]) raise return svn_rev def real_main(options, args):
def real_main(options, args): if run_hg(["st", "-m"]): print( "There are uncommitted changes. Either commit them or put " "them aside before running hgpushsvn.") return 1 if check_for_applied_patches(): print( "There are applied mq patches. Put them aside before " "running hgpushsvn.") return 1 svn_info = get_svn_info(".") svn_current_rev = svn_info["last_changed_rev"] # e.g. u'svn://svn.twistedmatrix.com/svn/Twisted' repos_url = svn_info["repos_url"] # e.g. u'svn://svn.twistedmatrix.com/svn/Twisted/branches/xmpp-subprotocols-2178-2' wc_url = svn_info["url"] assert wc_url.startswith(repos_url) # e.g. u'/branches/xmpp-subprotocols-2178-2' wc_base = wc_url[len(repos_url):] svn_branch = wc_url.split("/")[-1] # Get remote SVN info svn_greatest_rev = get_svn_info(wc_url)['last_changed_rev'] if svn_greatest_rev != svn_current_rev: # We can't go on if the pristine branch isn't up to date. # If the pristine branch lacks some revisions from SVN we are not # able to pull them afterwards. # For example, if the last SVN revision in out hgsvn repository is # r100 and the latest SVN revision is r101, hgpushsvn would create # a tag svn.102 on top of svn.100, but svn.101 isn't in hg history. print("Branch '%s' out of date. Run 'hgpullsvn' first." % svn_branch) return 1 # Switch branches if necessary. orig_branch = run_hg(["branch"]).strip() if orig_branch != svn_branch: if not hg_switch_branch(orig_branch, svn_branch): return 1 hg_start_rev = "svn.%d" % svn_current_rev hg_revs = None try: hg_start_cset = get_hg_cset(hg_start_rev) except RuntimeError: if not options.force: raise hg_start_cset = get_hg_cset("0") print "Warning: revision '%s' not found, forcing to first rev '%s'" % ( hg_start_rev, hg_start_cset) else: if not options.collapse: hg_revs = get_hg_revs(hg_start_cset, svn_branch) if hg_revs is None: hg_revs = [ strip_hg_rev(hg_start_cset), strip_hg_rev(get_hg_cset("tip")) ] pushed_svn_revs = [] try: if options.dryrun: print "Outgoing revisions that would be pushed to SVN:" try: for prev_rev, next_rev in get_pairs(hg_revs): if not options.dryrun: if not options.edit: ui.status("Committing changes up to revision %s", get_hg_cset(next_rev)) username = options.username if options.keep_author: username = run_hg( ["log", "-r", next_rev, "--template", "{author}"]) svn_rev = hg_push_svn(prev_rev, next_rev, edit=options.edit, username=username, password=options.password, cache=options.cache) if svn_rev: # Issue 95 - added update to prevent add/modify/delete crash run_svn(["up"]) map_svn_rev_to_hg(svn_rev, next_rev, local=True) pushed_svn_revs.append(svn_rev) else: print run_hg([ "log", "-r", next_rev, "--template", "{rev}:{node|short} {desc}" ]) except: # TODO: Add --no-backup to leave a "clean" repo behind if something # fails? run_hg(["revert", "--all"]) raise finally: work_branch = orig_branch or svn_branch if work_branch != svn_branch: run_hg(["up", "-C", work_branch]) run_hg(["branch", work_branch]) if pushed_svn_revs: if len(pushed_svn_revs) == 1: msg = "Pushed one revision to SVN: " else: msg = "Pushed %d revisions to SVN: " % len(pushed_svn_revs) run_svn(["up", "-r", pushed_svn_revs[-1]]) ui.status("%s %s", msg, ", ".join(str(x) for x in pushed_svn_revs)) for line in run_hg(["st"]).splitlines(): if line.startswith("M"): ui.status(("Mercurial repository has local changes after " "SVN update.")) ui.status(("This may happen with SVN keyword expansions.")) break elif not options.dryrun: ui.status("Nothing to do.")
def real_main(options, args): if run_hg(["st", "-m"]): print ("There are uncommitted changes. Either commit them or put " "them aside before running hgpushsvn.") return 1 if check_for_applied_patches(): print ("There are applied mq patches. Put them aside before " "running hgpushsvn.") return 1 svn_info = get_svn_info(".") svn_current_rev = svn_info["last_changed_rev"] # e.g. u'svn://svn.twistedmatrix.com/svn/Twisted' repos_url = svn_info["repos_url"] # e.g. u'svn://svn.twistedmatrix.com/svn/Twisted/branches/xmpp-subprotocols-2178-2' wc_url = svn_info["url"] assert wc_url.startswith(repos_url) # e.g. u'/branches/xmpp-subprotocols-2178-2' wc_base = wc_url[len(repos_url):] svn_branch = wc_url.split("/")[-1] # Get remote SVN info svn_greatest_rev = get_svn_info(wc_url)['last_changed_rev'] if svn_greatest_rev != svn_current_rev: # We can't go on if the pristine branch isn't up to date. # If the pristine branch lacks some revisions from SVN we are not # able to pull them afterwards. # For example, if the last SVN revision in out hgsvn repository is # r100 and the latest SVN revision is r101, hgpushsvn would create # a tag svn.102 on top of svn.100, but svn.101 isn't in hg history. print ("Branch '%s' out of date. Run 'hgpullsvn' first." % svn_branch) return 1 # Switch branches if necessary. orig_branch = run_hg(["branch"]).strip() if orig_branch != svn_branch: if not hg_switch_branch(orig_branch, svn_branch): return 1 hg_start_rev = "svn.%d" % svn_current_rev hg_revs = None try: hg_start_cset = get_hg_cset(hg_start_rev) except RuntimeError: if not options.force: raise hg_start_cset = get_hg_cset("0") print "Warning: revision '%s' not found, forcing to first rev '%s'" % ( hg_start_rev, hg_start_cset) else: if not options.collapse: hg_revs = get_hg_revs(hg_start_cset, svn_branch) if hg_revs is None: hg_revs = [strip_hg_rev(hg_start_cset), strip_hg_rev(get_hg_cset("tip"))] pushed_svn_revs = [] try: if options.dryrun: print "Outgoing revisions that would be pushed to SVN:" try: for prev_rev, next_rev in get_pairs(hg_revs): if not options.dryrun: if not options.edit: ui.status("Committing changes up to revision %s", get_hg_cset(next_rev)) username = options.username if options.keep_author: username = run_hg(["log", "-r", next_rev, "--template", "{author}"]) svn_rev = hg_push_svn(prev_rev, next_rev, edit=options.edit, username=username, password=options.password, cache=options.cache) if svn_rev: # Issue 95 - added update to prevent add/modify/delete crash run_svn(["up"]) map_svn_rev_to_hg(svn_rev, next_rev, local=True) pushed_svn_revs.append(svn_rev) else: print run_hg(["log", "-r", next_rev, "--template", "{rev}:{node|short} {desc}"]) except: # TODO: Add --no-backup to leave a "clean" repo behind if something # fails? run_hg(["revert", "--all"]) raise finally: work_branch = orig_branch or svn_branch if work_branch != svn_branch: run_hg(["up", "-C", work_branch]) run_hg(["branch", work_branch]) if pushed_svn_revs: if len(pushed_svn_revs) == 1: msg = "Pushed one revision to SVN: " else: msg = "Pushed %d revisions to SVN: " % len(pushed_svn_revs) run_svn(["up", "-r", pushed_svn_revs[-1]]) ui.status("%s %s", msg, ", ".join(str(x) for x in pushed_svn_revs)) for line in run_hg(["st"]).splitlines(): if line.startswith("M"): ui.status(("Mercurial repository has local changes after " "SVN update.")) ui.status(("This may happen with SVN keyword expansions.")) break elif not options.dryrun: ui.status("Nothing to do.")