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 adjust_executable_property(files): execflags = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH svn_map = {} for fname in files: if run_svn(['propget', 'svn:executable', fname], mask_atsign=True).strip(): svn_map[fname] = True else: svn_map[fname] = False for fname in files: m = os.stat(fname).st_mode & 0777 is_exec = bool(m & execflags) if is_exec and not svn_map[fname]: run_svn(['propset', 'svn:executable', 'ON', fname], mask_atsign=True) elif not is_exec and svn_map[fname]: run_svn(['propdel', 'svn:executable', fname], mask_atsign=True)
def svn_checkout(svn_url, checkout_dir, rev_number=None): """ Checkout the given URL at an optional revision number. """ args = [] if rev_number is not None: args += ['-r', rev_number] args += [svn_url, checkout_dir] return run_svn(svn_checkout_args + args)
def do_svn_copy(src, dest): """ Call Svn copy command to record copying file src to file dest. If src is present then backup src before other tasks. Before issuing copy command move dest to src and remove src after """ backup_src = '' if os.path.exists(src): backup_src = os.path.join(hgsvn_private_dir, "hgpushsvn_backup.tmp") os.rename(src, backup_src) try: try: # We assume that src is cotrolled by svn os.rename(dest, src) # Create svn subdirectories if needed # We know that subdirectories themselves are present # as dest is present pattern = re.compile(r"[/\\]") pos = 0 while(True): match = pattern.search(dest, pos + 1) if match == None: break pos = match.start() run_svn(['add', '--depth=empty'], [dest[:pos]], mask_atsign=True) pos = match.end() - 1 # Do the copy itself run_svn(['copy', src, dest], mask_atsign=True) except ExternalCommandFailed: # Revert rename os.rename(src, dest) finally: if os.path.isfile(src): os.remove(src) if(backup_src): os.rename(backup_src, src)
def do_svn_copy(src, dest): """ Call Svn copy command to record copying file src to file dest. If src is present then backup src before other tasks. Before issuing copy command move dest to src and remove src after """ backup_src = '' if os.path.exists(src): backup_src = os.path.join(hgsvn_private_dir, "hgpushsvn_backup.tmp") os.rename(src, backup_src) try: try: # We assume that src is cotrolled by svn os.rename(dest, src) # Create svn subdirectories if needed # We know that subdirectories themselves are present # as dest is present pattern = re.compile(r"[/\\]") pos = 0 while (True): match = pattern.search(dest, pos + 1) if match == None: break pos = match.start() run_svn(['add', '--depth=empty'], [dest[:pos]], mask_atsign=True) pos = match.end() - 1 # Do the copy itself run_svn(['copy', src, dest], mask_atsign=True) except ExternalCommandFailed: # Revert rename os.rename(src, dest) finally: if os.path.isfile(src): os.remove(src) if (backup_src): os.rename(backup_src, src)
def run_svn_log(svn_url, rev_start, rev_end, limit, stop_on_copy=False): """ Fetch up to 'limit' SVN log entries between the given revisions. """ if stop_on_copy: args = ['--stop-on-copy'] else: args = [] args += ['-r', '%s:%s' % (rev_start, rev_end), '--limit', limit, svn_url] xml_string = run_svn(svn_log_args + args) return parse_svn_log_xml(xml_string)
def get_svn_client_version(): """Returns the SVN client version as a tuple. The returned tuple only contains numbers, non-digits in version string are silently ignored. """ global _svn_client_version if _svn_client_version is None: raw = run_svn(['--version', '-q']).strip() _svn_client_version = tuple(map(int, [x for x in raw.split('.') if x.isdigit()])) return _svn_client_version
def get_svn_client_version(): """Returns the SVN client version as a tuple. The returned tuple only contains numbers, non-digits in version string are silently ignored. """ global _svn_client_version if _svn_client_version is None: raw = run_svn(['--version', '-q']).strip() _svn_client_version = tuple( map(int, [x for x in raw.split('.') if x.isdigit()])) return _svn_client_version
def get_svn_status(svn_wc, quiet=False): """ Get SVN status information about the given working copy. """ # Ensure proper stripping by canonicalizing the path svn_wc = os.path.abspath(svn_wc) args = [svn_wc] if quiet: args += ['-q'] else: args += ['-v'] xml_string = run_svn(svn_status_args + args) return parse_svn_status_xml(xml_string, svn_wc, ignore_externals=True)
def get_svn_info(svn_url_or_wc, rev_number=None): """ Get SVN information for the given URL or working copy, with an optionally specified revision number. Returns a dict as created by parse_svn_info_xml(). """ if rev_number is not None: args = ['-r', rev_number] else: args = [] xml_string = run_svn(svn_info_args + args + [svn_url_or_wc], fail_if_stderr=True) return parse_svn_info_xml(xml_string)
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)
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): if check_for_applied_patches(): print ("There are applied mq patches. Put them aside before " "running hgpullsvn.") return 1
def test_svn(self): s = common.run_svn(['--version', '-q']) eq_(s.split('.')[0], '1')
def hg_push_svn(start_rev, end_rev, edit, username, password, cache): """ Commit the changes between two hg revisions into the SVN repository. Returns the SVN revision object, or None if nothing needed checking in. """ added, removed, modified, copied = get_hg_changes(start_rev+':'+end_rev) # Before replicating changes revert directory to previous state... run_hg(['revert', '--all', '--no-backup', '-r', end_rev]) # ... and restore .svn directories, if we lost some of them due to removes run_svn(['revert', '--recursive', '.']) # Do the rest over up-to-date working copy # Issue 94 - moved this line to prevent do_svn_copy crashing when its not the first changeset run_hg(["up", "-C", end_rev]) # Record copyies into svn for dest, src in copied.iteritems(): do_svn_copy(src,dest) # Add new files and dirs if added: bulk_args = get_ordered_dirs(added) + added run_svn(["add", "--depth=empty"], bulk_args, mask_atsign=True) removed = cleanup_svn_unversioned(removed) modified = cleanup_svn_unversioned(modified) # Remove old files and empty dirs if removed: empty_dirs = [d for d in reversed(get_ordered_dirs(removed)) if not run_hg(["st", "-c", "%s" %d])] # When running "svn rm" all files within removed folders needs to # be removed from "removed". Otherwise "svn rm" will fail. For example # instead of "svn rm foo/bar foo" it should be "svn rm foo". # See issue15. svn_removed = strip_nested_removes(removed + empty_dirs) run_svn(["rm"], svn_removed, mask_atsign=True) if added or removed or modified: svn_sep_line = "--This line, and those below, will be ignored--\n" adjust_executable_property(added+modified) # issue24 description = get_hg_csets_description(start_rev, end_rev) fname = os.path.join(hgsvn_private_dir, 'commit-%s.txt' % end_rev) lines = description.splitlines()+[""] lines.append(svn_sep_line) lines.append("To cancel commit just delete text in top message part") lines.append("") lines.append("Changes to be committed into svn:") lines.extend([item.decode("utf-8") for item in run_svn(["st", "-q"]).splitlines()]) lines.append("") lines.append(("These changes are produced from the following " "Hg changesets:")) lines.extend(get_hg_log(start_rev, end_rev).splitlines()) f = codecs.open(fname, "wb", "utf-8") f.write(os.linesep.join(lines)) f.close() try: if edit: editor=(os.environ.get("HGEDITOR") or os.environ.get("SVNEDITOR") or os.environ.get("VISUAL") or os.environ.get("EDITOR", "vi")) rc = os.system("%s \"%s\"" % (editor, fname) ) if(rc): raise ExternalCommandFailed("Can't launch editor") empty = True f=open(fname, "r") for line in f: if(line == svn_sep_line): break if(line.strip() != ""): empty = False break f.close() if(empty): raise EmptySVNLog("Commit cancelled by user\n") svn_rev = None svn_commands = ["commit", "-F", fname, "--encoding", get_encoding()] if username is not None: svn_commands += ["--username", username] if password is not None: svn_commands += ["--password", password] if cache: svn_commands.append("--no-auth-cache") out = run_svn(svn_commands) outlines = out.splitlines() outlines.reverse() for line in outlines: # one of the last lines holds the revision. # The previous approach to set LC_ALL to C and search # for "Committed revision XXX" doesn't work since # svn is unable to convert filenames containing special # chars: # http://svnbook.red-bean.com/en/1.2/svn.advanced.l10n.html match = re.search("(\d+)", line) if match: svn_rev = int(match.group(1)) break return svn_rev finally: # Exceptions are handled by main(). os.remove(fname) else: print "*", "svn: nothing to do" return None
def main(): usage = """1. %prog [-r SVN rev] [-p SVN peg rev] <SVN URL> [local checkout] 2. %prog --local-only <local checkout>""" parser = OptionParser(usage) parser.add_option("-r", "--svn-rev", type="int", dest="svn_rev", help="SVN revision to checkout from") parser.add_option("-p", "--svn-peg", type="int", dest="svn_peg", help="SVN peg revision to locate checkout URL") parser.add_option("--no-hgignore", dest="hgignore", action="store_false", default=True, help="Don't autogenerate .hgignore") parser.add_option("--local-only", action="store_true", default=False, help="Don't use the server, just build from a local checkout") parser.add_option("--branch", type="string", dest="svn_branch", help="Override branch name (defaults to last path component of <SVN URL>)") (options, args) = run_parser(parser, __doc__) if not 1 <= len(args) <= 2: display_parser_error(parser, "incorrect number of arguments") target_svn_url = args.pop(0).rstrip("/") local_repo = args and args.pop(0) or None if options.svn_peg: target_svn_url += "@" + str(options.svn_peg) if options.local_only: if options.svn_peg: display_parser_error(parser, "--local-only and --svn-peg are mutually exclusive") if options.svn_rev: display_parser_error(parser, "--local-only and --svn-rev are mutually exclusive") if local_repo: display_parser_error(parser, "--local-only does not accept a target directory") # Get SVN info svn_info = get_svn_info(target_svn_url, options.svn_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' actual_svn_url = svn_info['url'] assert actual_svn_url.startswith(repos_url) # if we are allowed to go to the server, lets use it if options.local_only: target_svn_url = os.path.abspath(target_svn_url) local_repo = target_svn_url # e.g. u'/branches/xmpp-subprotocols-2178-2' svn_path = actual_svn_url[len(repos_url):] # e.g. 'xmpp-subprotocols-2178-2' svn_branch = actual_svn_url.split("/")[-1] # if --branch was passed, override the branch name derived above if options.svn_branch: svn_branch = options.svn_branch svn_greatest_rev = svn_info['last_changed_rev'] if options.svn_peg: actual_svn_url += "@" + str(options.svn_peg) if not local_repo: local_repo = actual_svn_url.split("/")[-1] if os.path.exists(local_repo): if not os.path.isdir(local_repo): raise ValueError("%s is not a directory" % local_repo) is_svn_dir = os.path.isdir(os.path.join(local_repo, '.svn')) if is_svn_dir and not options.local_only: nlocal_repo = os.path.abspath(local_repo) print "%s is already a SVN checkout." % nlocal_repo sys.exit(1) elif not is_svn_dir and options.local_only: nlocal_repo = os.path.abspath(local_repo) print "%s must be a SVN checkout for --local-only" % nlocal_repo sys.exit(1) elif options.local_only and is_svn_dir: status = get_svn_status(local_repo, quiet=True) if status: print ("There are uncommitted changes. Either commit them " "or put them aside before running hgimportsvn.") sys.exit(1) else: os.mkdir(local_repo) fixup_hgsvn_dir(local_repo) os.chdir(local_repo) # Get log entry for the SVN revision we will check out svn_copyfrom_path = None svn_copyfrom_revision = None if options.local_only or svn_greatest_rev == 0: # fake up a log message for the initial commit svn_start_log = {} svn_start_log['message'] = 'initial import by hgimportsvn' svn_start_log['revision'] = svn_info['last_changed_rev'] svn_start_log['author'] = svn_info.get('last_changed_author') svn_start_log['date'] = svn_info['last_changed_date'] elif options.svn_rev: # If a specific rev was requested, get log entry just before or at rev svn_start_log = get_last_svn_log_entry(actual_svn_url, 1, options.svn_rev) else: # Otherwise, get log entry of branch creation svn_start_log = get_first_svn_log_entry(actual_svn_url, 1, svn_greatest_rev) for p in svn_start_log['changed_paths']: if p['path'] == svn_path: svn_copyfrom_path = p['copyfrom_path'] svn_copyfrom_revision = p['copyfrom_revision'] break if svn_copyfrom_path: print "SVN branch was copied from '%s' at rev %s" % ( svn_copyfrom_path, svn_copyfrom_revision) else: print "SVN branch isn't a copy" # This is the revision we will checkout from svn_rev = svn_start_log['revision'] # Initialize hg repo if not os.path.exists(".hg"): run_hg(["init"]) if svn_copyfrom_path: # Try to find an hg repo tracking the SVN branch which was copied copyfrom_branch = svn_copyfrom_path.split("/")[-1] hg_copyfrom = os.path.join("..", copyfrom_branch) if (os.path.exists(os.path.join(hg_copyfrom, ".hg")) and os.path.exists(os.path.join(hg_copyfrom, svn_private_dir))): u = get_svn_info(hg_copyfrom)['url'] if u != repos_url + svn_copyfrom_path: print "SVN URL %s in working copy %s doesn't match, ignoring" % (u, hg_copyfrom) else: # Find closest hg tag before requested SVN rev best_tag = None for line in run_hg(["tags", "-R", hg_copyfrom]).splitlines(): if not line.startswith("svn."): continue tag = line.split(None, 1)[0] tag_num = int(tag.split(".")[1]) if tag_num <= svn_copyfrom_revision and (not best_tag or best_tag < tag_num): best_tag = tag_num if not best_tag: print "No hg tag matching rev %s in %s" % (svn_rev, hg_copyfrom) else: # Don't use "pull -u" because it fails with hg 0.9.5 # ("branch default not found") run_hg(["pull", "-r", "svn.%d" % best_tag, hg_copyfrom]) # Not specifying "tip" fails with hg 0.9.5 # ("branch default not found") run_hg(["up", "tip"]) run_hg(["branch", svn_branch]) # Stay on the same filesystem so as to have fast moves if options.local_only: checkout_dir = target_svn_url else: checkout_dir = tempfile.mkdtemp(dir=".") try: # Get SVN manifest and checkout if not options.local_only: svn_checkout(target_svn_url, checkout_dir, svn_rev) svn_manifest = get_svn_versioned_files(checkout_dir) svn_files = set(skip_dirs(svn_manifest, checkout_dir)) svn_dirs = sorted(set(svn_manifest) - svn_files) svn_files = list(svn_files) # in the local case the files here already if not options.local_only: # All directories must exist, including empty ones # (both for hg and for moving .svn dirs later) for d in svn_dirs: if not os.path.isdir(d): if os.path.exists(d): os.remove(d) os.mkdir(d) # Replace checked out files for f in svn_files: if os.path.exists(f): os.remove(f) os.rename(os.path.join(checkout_dir, f), f) try: # Add/remove new/old files if svn_files: run_hg(["addremove"] + hg_exclude_options, svn_files) hg_commit_from_svn_log_entry(svn_start_log) #hg_commit_from_svn_log_entry(svn_start_log, svn_files) except: run_hg(["revert", "--all"]) raise # in the local case the files here already if not options.local_only: # Move SVN working copy here (don't forget base directory) for d in chain([""], svn_dirs): os.rename(os.path.join(checkout_dir, d, svn_private_dir), os.path.join(d, svn_private_dir)) finally: # in the local case the checkout_dir is the target, so don't delete it if not options.local_only: rmtree(checkout_dir) if options.hgignore: svn_ignores = [] # we use '.' because that it the location of the hg repo that we are # building and at this point we have already copied all of svn to the # checkout (for both the local and non-local case) for (path, dirs, files) in os.walk('.'): if '.svn' in dirs: dirs.remove('.svn') # the [2:] strips off the initial './' local_ignores = [os.path.join(path, s.strip())[2:] for s in run_svn(['propget', 'svn:ignore', path], mask_atsign=True).splitlines() if bool(s.strip()) ] svn_ignores += local_ignores else: # if we are not inside an svn world # we can just bail del dirs[:] # Generate .hgignore file to ignore .svn and .hgsvn directories f = open(".hgignore", "a") try: f.write("\n# Automatically generated by `hgimportsvn`\n") f.write("syntax:glob\n%s\n" % "\n".join([svn_private_dir, hgsvn_private_dir])) f.write("\n# These lines are suggested according to the svn:ignore property") f.write("\n# Feel free to enable them by uncommenting them") f.write("\nsyntax:glob\n") f.write("".join("#%s\n" % s for s in svn_ignores)) finally: f.close() print "Finished! You can now pull all SVN history with 'hgpullsvn'."
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.")
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): if check_for_applied_patches(): print("There are applied mq patches. Put them aside before " "running hgpullsvn.") return 1
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)
def hg_push_svn(start_rev, end_rev, edit, username, password, cache): """ Commit the changes between two hg revisions into the SVN repository. Returns the SVN revision object, or None if nothing needed checking in. """ added, removed, modified, copied = get_hg_changes(start_rev + ':' + end_rev) # Before replicating changes revert directory to previous state... run_hg(['revert', '--all', '--no-backup', '-r', end_rev]) # ... and restore .svn directories, if we lost some of them due to removes run_svn(['revert', '--recursive', '.']) # Do the rest over up-to-date working copy # Issue 94 - moved this line to prevent do_svn_copy crashing when its not the first changeset run_hg(["up", "-C", end_rev]) # Record copyies into svn for dest, src in copied.iteritems(): do_svn_copy(src, dest) # Add new files and dirs if added: bulk_args = get_ordered_dirs(added) + added run_svn(["add", "--depth=empty"], bulk_args, mask_atsign=True) removed = cleanup_svn_unversioned(removed) modified = cleanup_svn_unversioned(modified) # Remove old files and empty dirs if removed: empty_dirs = [ d for d in reversed(get_ordered_dirs(removed)) if not run_hg(["st", "-c", "%s" % d]) ] # When running "svn rm" all files within removed folders needs to # be removed from "removed". Otherwise "svn rm" will fail. For example # instead of "svn rm foo/bar foo" it should be "svn rm foo". # See issue15. svn_removed = strip_nested_removes(removed + empty_dirs) run_svn(["rm"], svn_removed, mask_atsign=True) if added or removed or modified: svn_sep_line = "--This line, and those below, will be ignored--\n" adjust_executable_property(added + modified) # issue24 description = get_hg_csets_description(start_rev, end_rev) fname = os.path.join(hgsvn_private_dir, 'commit-%s.txt' % end_rev) lines = description.splitlines() + [""] lines.append(svn_sep_line) lines.append("To cancel commit just delete text in top message part") lines.append("") lines.append("Changes to be committed into svn:") lines.extend([ item.decode("utf-8") for item in run_svn(["st", "-q"]).splitlines() ]) lines.append("") lines.append(("These changes are produced from the following " "Hg changesets:")) lines.extend(get_hg_log(start_rev, end_rev).splitlines()) f = codecs.open(fname, "wb", "utf-8") f.write(os.linesep.join(lines)) f.close() try: if edit: editor = (os.environ.get("HGEDITOR") or os.environ.get("SVNEDITOR") or os.environ.get("VISUAL") or os.environ.get("EDITOR", "vi")) rc = os.system("%s \"%s\"" % (editor, fname)) if (rc): raise ExternalCommandFailed("Can't launch editor") empty = True f = open(fname, "r") for line in f: if (line == svn_sep_line): break if (line.strip() != ""): empty = False break f.close() if (empty): raise EmptySVNLog("Commit cancelled by user\n") svn_rev = None svn_commands = [ "commit", "-F", fname, "--encoding", get_encoding() ] if username is not None: svn_commands += ["--username", username] if password is not None: svn_commands += ["--password", password] if cache: svn_commands.append("--no-auth-cache") out = run_svn(svn_commands) outlines = out.splitlines() outlines.reverse() for line in outlines: # one of the last lines holds the revision. # The previous approach to set LC_ALL to C and search # for "Committed revision XXX" doesn't work since # svn is unable to convert filenames containing special # chars: # http://svnbook.red-bean.com/en/1.2/svn.advanced.l10n.html match = re.search("(\d+)", line) if match: svn_rev = int(match.group(1)) break return svn_rev finally: # Exceptions are handled by main(). os.remove(fname) else: print "*", "svn: nothing to do" return None