"""hgpullsvn must be run in a repository created by hgimportsvn. It pulls
new revisions one by one from the SVN repository and converts them into local
Mercurial changesets.

"""

from hgsvn import ui
from hgsvn.shell  import (once_or_more)
from hgsvn.hgclient import *
from hgsvn.common import (
    hg_commit_from_svn_log_entry, hg_exclude_options,
    get_svn_rev_from_hg, hg_switch_branch, hg_is_clean,
    check_for_applied_patches,
      check_branchmap
    , load_hgsvn_branches_map
    , get_hg_cset, strip_hg_rev
    , tag_of_svn, get_hg_cset_of_svn
    , hg_tag_svn_rev, hg_rev_tag_by_svn
    , svn_branch_of_path
    , svn_base_of_branch
    , is_hg_have_branch
    , hg_force_switch_branch
    , get_svn_rev_of_hgbranch
    , hg_subrepos, save_hg_subrepos
    , in_pathes
    , drop_nested_dirs
)
from hgsvn.svnclient import (
    get_svn_info, get_svn_versioned_files, iter_svn_log_entries,
    get_svn_client_version, get_svn_status
    , run_svn
    , svn_switch_to
    , get_first_svn_log_entry
    , svn_revert_all
    , svn_is_clean
)
from hgsvn.errors import (
    ExternalCommandFailed, OverwrittenSVNBranch, UnsupportedSVNAction
    , RunCommandError
)
from hgsvn.run.common import run_parser, display_parser_error
from hgsvn.run.common import locked_main

import sys
import os
import time
import traceback
from optparse import OptionParser
import copy
import re

"""
NOTE: interesting tests:
    * http://svn.python.org/projects/python/trunk/Mac :
        - files with space characters in them just before 45000
        - file and dir renames/removes between 46701 and 46723
"""

repos_url = ""
hgsubrepos = list()

# TODO: an option to enable/disable svn:externals (disabled by default?)

def IsDebug():
    return (ui._level >= ui.DEBUG)


def detect_overwritten_svn_branch(wc_url, svn_rev):
    """
    Detect whether the current SVN branch was in a different location at
    the given revision. This means the current branch was later overwritten
    by another one.
    """
    remote_url = get_svn_info(wc_url, svn_rev)['url']
    if remote_url != wc_url:
        msg = ("The current branch (%s) has been\noverwritten with contents from %s.\n"
            + "hgsvn is unable to fetch history of the original branch.\n"
            + "Also, you will have to do a separate 'hgsvnimport' to pull recent history.\n"
            ) % (wc_url, remote_url)
        raise OverwrittenSVNBranch(msg)

def check_hgsub(svn_wc, wc_path):
    global hgsubrepos
    if in_pathes(wc_path, hgsubrepos):
        return True
    hgpath = os.path.join(svn_wc, wc_path, ".hg")
    if os.path.dirname(hgpath) or os.path.exists(hgpath):
        hgsubrepos.append(wc_path)
        return True
    return False

def hg_merge_2wc(rev_number):
    def noninderactive_delete(size, output):
        """
        on prompt appears:
        use (c)hanged version or (d)elete?
        answer delete
        """
        from hglib import util
        ui.status("merge asks:%s"%output)
        found =  re.search("(d)elete", output)
        if found is None:
            return util.b('')
        ui.status("merge answer d")
        return util.b("d\n")

    if rev_number is None:
        return
    args = ["merge","-f","--tool","internal:local","-r", rev_number]
    return run_hg(args, prompt = noninderactive_delete)

def hg_resolve_all():
    args = ["resolve","--all"]
    return run_hg(args)

def autostart_branch(svn_branch, hg_branch, target_revno, options):
    #take svn_branch origin and ensures it pull in hg, and mark it with branch-head tag
    # branch-head - is starting rev which is copy of original
    global repos_url

    ui.status("starting new branch %s from %s@%d"%(hg_branch, svn_branch, target_revno));
    svn_switch_to(repos_url+svn_branch)
    origin = get_first_svn_log_entry('.', 0, target_revno)
    svnrev = origin['revision']
    hgrevid = None
    try:
        hgrevid = get_hg_cset_of_svn(svnrev)
        if (len(hgrevid) == 0):
            raise
        return svnrev
    except:
        # there is no svnbranch origin in hg, check ones source
        paths = origin['changed_paths']
        if len(paths) == 0:
            ui.status("cant determine branch %s origin source"%svn_branch)
            return -1
        path = paths[0]
        source = path['copyfrom_revision']
        if source:
            # now get origin`s source hg-rev, and mark it with origin svntag
            ui.status("branch svn:%s sourced from rev%s"%(svn_branch, source), level = ui.VERBOSE)
            srcid = None
            try:
                srcid = get_hg_cset_of_svn(source)
                if (len(srcid) == 0):
                    raise
            except:
                #there is no source in hg pulled, so try to pull it
                if not pull_branch(get_last_svn_log_entry("." , source, source), options):
                    return -2
                try:
                    srcid = get_hg_cset_of_svn(source)
                except:
                    ui.status("svn:%s still not pulled"%source)
                    return -3
            hg_rev_tag_by_svn(strip_hg_rev(srcid), svnrev);
            return svnrev

    ui.status("cant determine branch svn:%s source"%(svn_branch))
    return -4

def pull_branch(log_entry, options):
        global repos_url

        svn_rev = log_entry['revision'];
        paths = log_entry['changed_paths']
        if len(paths) == 0:
            ui.status("strange svn rev: - with no changes")
            return False
        headpath = paths[0]['path']
        svn_branch, hg_branch = svn_branch_of_path(headpath)
        if svn_branch is None:
            ui.status("cant mutch path %s for branch"%(headpath), level = ui.VERBOSE)
            return False
        ui.status("mutched for branch svn:%s hg:%s"%(svn_branch, hg_branch), level = ui.VERBOSE)

        localops = copy.deepcopy(options)
        localops.svn_rev = svn_rev
        if is_hg_have_branch(hg_branch):
            origin_pt   = get_svn_rev_of_hgbranch(hg_branch)
            svn_origin  = origin_pt.svnrev
            hg_origin   = origin_pt.hgrev
            if svn_origin is None:
                ui.status("hg alredy have branch %s not synched with svn"%hg_branch);
                return False
        else:
            # actualy hg have no such branch so start it from stoponcopy origin
            svn_origin = autostart_branch(svn_branch, hg_branch, svn_rev, localops)
            hg_origin = get_hg_cset_of_svn(svn_origin)

        ok = False
        if svn_origin > 0:
            svn_switch_to(repos_url+svn_branch, rev_number=svn_origin, clean=True)
            hg_force_switch_branch( hg_branch, strip_hg_rev(hg_origin) )
            localops.force = True
            ok = (real_main(localops, {}) == 0)

        del localops
        return ok

def svn_sync_merge_hg(log_entry, current_rev, svn_wc, wc_url, wc_base, hg_branch, options):
    #   check all parents for branch name, and try to push one branches if can
    #   after all branches complete, merge svn work copy and resolves it for hg_push_svn
    
    # ... and restore .svn directories, if we lost some of them due to removes
    #run_svn(['revert', '--recursive', '.'])
    uiMERGE = ui.DEFAULT
    
    svn_rev = log_entry['revision']
    merge_revs = log_entry['merges']
    merge_parent = merge_revs[0]
    merge_hgrevid = None
    try:
        merge_hgrevid = get_hg_cset_of_svn(merge_parent.revno)
        ui.status("merge for parents: svn:%s hg:%s"%(merge_parent.revno, merge_hgrevid), level=uiMERGE)
    except:
        pass

    if (merge_hgrevid is None) or (len(merge_hgrevid) == 0):
        ui.status("have no pulled parent svn:%s"%merge_parent.revno, level = uiMERGE)
        #try to match parent path with branches list
        if pull_branch(merge_parent.entry, options):
            #switch back to current branch for merging here
            svn_switch_to(wc_url, rev_number=current_rev, clean=True)
            if not hg_force_switch_branch(hg_branch, tag_of_svn(current_rev)):
                raise
        else:
            return False
        merge_hgrevid = get_hg_cset_of_svn(merge_parent.revno)
        ui.status("merge for parents: svn:%s hg:%s"%(merge_parent.revno, merge_hgrevid), level = uiMERGE)

    hg_merge_2wc( strip_hg_rev(merge_hgrevid) )
    hg_resolve_all()
    # and pull like normal svn up
    svn_revert_all();

    return True

def protect_space_path(src):
    if (' ' in src):
        return '"'+src+'"';
    return src

def protect_space_pathes(src):
    res = []
    for path in src:
        res.append(protect_space_path(path))
    return res

def hg_get_status_file(fname):
    args = ["st", "-A", fname]
    try:
        out = run_hg(args, output_is_locale_encoding=True)
        if len(out) <= 0:
            return None
        lines = out.splitlines()
        line = lines[0]
        if line[1] ==' ' :
            return line[0]
        return None
    except (RunCommandError) as e:
        return None

def pull_svn_rev(log_entry, current_rev, svn_wc, wc_url, wc_base, options):
    """
    Pull SVN changes from the given log entry.
    Returns the new SVN revision. If an exception occurs, it will rollback
    to revision 'current_rev'.
    """
    global hgsubrepos
    
    retry = options.svnretry
    svn_rev = log_entry['revision']

    added_paths = []
    copied_paths = []
    removed_paths = []
    changed_paths = []
    unrelated_paths = []
    replaced_paths = {}

    copied_dirs = []
    removed_dirs = []
    

    # 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("/")

        if options.subrepo == "skip":
            tmpp = os.path.normcase(os.path.normpath(p))
            if in_pathes(tmpp, hgsubrepos):
                ui.status("skip action on path %s of nested repo" % (p))
                unrelated_paths.append(p)
                continue

        # Record for commit
        changed_paths.append(p)
        if IsDebug():
            ui.status("action %s on path %s" % (action, 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("/")
            if IsDebug():
                ui.status("    is copyed %s" % (old_p))
            # 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)
                    copied_dirs.append(p)
                    if action == 'R':
                        removed_dirs.append(p)
                    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 action == 'A':
            added_paths.append(p)
        elif action == 'D':
            # Same as above: unfold directories into their affected files
            if not os.path.isdir(p):
                removed_paths.append(p)
            else:
                removed_dirs.append(p)
        elif 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)

    added_dirs = drop_nested_dirs(added_paths)
    new_dirs = added_dirs+copied_dirs

    # Ignore the file if it was in a directory that was moved as part of this same commit
    if removed_paths:
        removed_paths = drop_nested_dirs(removed_paths, new_dirs)
    if removed_dirs:
        removed_dirs = drop_nested_dirs(removed_dirs, new_dirs)
        for p in removed_dirs:
            r = run_hg(["st", "-nc"], [p], output_is_locale_encoding=True)
            for f in r.splitlines():
                removed_paths.append(f)

    # 2. Update SVN + add/remove/commit hg
    try:
      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 = []
            update_status = get_svn_status('.', quiet=True)
            for status_entry in update_status:
                if status_entry['type'] == 'normal':
                    #if status_entry['status'] != 'unversioned':
                        conflicts.append( status_entry['status'] + ':' + 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.')
                svn_revert_all()

            # replased_paths reorganise into composition of added+removed files, because it is new dir over old one
            # possibly need to mark all other files in dir as modified?
            # possibly it better to make full dir forget + add ?
            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)
            
            #maybe remove at first because we have delete +copy/add  in special cases
                           
            #since we have 2 parts  of new folders - added and copyed:
            #   a - we cant adds that in copyed before copy
            #   b - we cant copy to folders that not beed added before
            # but hg not take into acount folder, so it can addremove even not previously added folder, therefore
            #   we can addremove directly baladahead through recursive add/copy 
            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 ) #protect_space_pathes
                
            for old, new in copied_paths:
                try:
                    if not os.path.islink(new) or os.path.exists(new):
                        run_hg(["copy", "-A"], [old, new])
                    else:
                        run_hg(["copy"], [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():
                        ui.status("The old path %s is known by hg, something else happened."%old)
                        raise
                    run_hg(["add"], [new])
                        
            if removed_paths:
                for file_path in removed_paths:
                    try:
                        run_hg(["remove", "-A"], [file_path])
                    except (ExternalCommandFailed) as e:
                        if str(e).find("not removing") > 0: #"file still exists"
                            ui.status("failed to delete file %s, try to forget it" % file_path, level=ui.VERBOSE)
                            run_hg(["forget"], [file_path])
                        elif options.on_noversioned_change == 'skip':
                            if str(e).find("file is untracked") > 0:
                                ui.status("Ignoring warnings about untracked files: '%s'" % str(e), level=ui.WARNING)
                            else:
                                #check that file is untracked and absent, on such files hg failed
                                status = hg_get_status_file(file_path)
                                if status is None:
                                    ui.status("Ignoring warnings about untracked files: '%s'" % file_path, level=ui.WARNING)
                                else:
                                    raise
                        else:
                            ui.status("possibly affected unversioned file '%s'. look help on <--noversioned_change>" % file_path, level=ui.ERROR)
                            raise
            hg_commit_from_svn_log_entry(log_entry)
        else:
            if unrelated_paths:
                detect_overwritten_svn_branch(wc_url, svn_rev)
            ui.status("!!!Nothing commited on rev%s"%svn_rev, level=ui.WARNING)
            hg_tag_svn_rev(svn_rev)
            
      except (ExternalCommandFailed) as e:
        if str(e).find("inside nested repo") > 0:
            if options.subrepo != "skip":
                raise
            ui.status("\n try skip of nested repo affects", level=ui.VERBOSE)
            nestpath = re.search(r"path '(?P<npath>\S+)'", str(e))
            nestrepo = re.search("repo '(?P<nrepo>\S+)'", str(e))
            if nestpath is None:
                ui.status("uncknown abort of nested repo output format");
                raise
            npath = nestpath.group("npath")
            nrepo = nestrepo.group("nrepo")
            nrepo = os.path.normcase(os.path.normpath(nrepo))
            if nrepo in hgsubrepos:
                ui.status("\n try path:%s of nested repo:%s alredy skip, and it not help" % (npath, nrepo))
                raise
            ui.status("\n try skip path:%s of nested repo:%s" % (npath, nrepo), level=ui.VERBOSE)
            hgsubrepos.append(nrepo)
            return pull_svn_rev(log_entry, current_rev, svn_wc, wc_url, wc_base, options)
        else:
            raise

    # NOTE: in Python 2.5, KeyboardInterrupt isn't a subclass of Exception anymore
    except (Exception, KeyboardInterrupt) as e:
        ui.status("\nInterrupted, please wait for cleanup!\n", level=ui.ERROR)
        state = run_hg(["status", "-v"])
        ui.status("hg:\n"+state, 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"])
        raise
        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):
    global repos_url

    if check_for_applied_patches():
        print_("There are applied mq patches. Put them aside before running hgpullsvn.")
        return 1
    svn_wc = "."
    orig_branch = run_hg(["branch"]).strip()

    is_at_pull_point = False;
    while True:
        # 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]
        svn_branch = check_branchmap(svn_branch, wc_base, options)
        
        if is_at_pull_point: 
            break
        is_at_pull_point = True;
        
        if options.prefere2hg and not options.svn_peg:
            # Prepare and/or switch named branches
            if orig_branch != svn_branch:
                if hg_is_clean(orig_branch):
                # Update to or create the "pristine" branch
                    svn_base = svn_base_of_branch(orig_branch)
                    ui.status("follow hg:%s check svn:%s"%(orig_branch, svn_base));
                    syncrev  = get_svn_rev_of_hgbranch(orig_branch)
                    ui.status("looks branch %s : (%s)"%(orig_branch, syncrev), level = ui.DEBUG);
                    svn_syncrev = syncrev.svnrev
                    hg_syncrev  = syncrev.hgrev
                    if not (svn_syncrev is None):
                        ui.status("svn follow hg(%s-%s) to %s@%d"%(orig_branch, hg_syncrev, svn_base, svn_syncrev));
                        run_hg(["update", "-C", "svn.%d" % svn_syncrev])
                        svn_switch_to(repos_url+'/'+svn_base, svn_syncrev, clean=False, ignore_ancetry=True)
                        continue;
                    else:
                        ui.status("looks branch %s not sync with svn (%s)"%(orig_branch, syncrev), level = ui.WARNING);
                else:
                    ui.status("hg branch %s not clean!!!", orig_branch , level=ui.VERBOSE)
        break
    
    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']

    synched_svnrev = None
    synched_hgrev  = None
    if options.prefere2hg and not options.svn_peg:
        #try to go to latest synched revision
        if orig_branch == svn_branch:
            if hg_is_clean(orig_branch):
                syncrev  = get_svn_rev_of_hgbranch(orig_branch)
                ui.status("looks branch %s sync at: (%s)"%(orig_branch, syncrev), level = ui.DEBUG);
                synched_svnrev = syncrev.svnrev
                synched_hgrev  = syncrev.hgrev
                if not (synched_svnrev is None):
                    next_rev = synched_svnrev+1
                    ui.status("pull starts from svn@%s"%next_rev, level = ui.DEBUG)

    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
    ui.status("check same branch now:%s requred:%s"%(orig_branch, svn_branch), level=ui.DEBUG)
    if orig_branch != svn_branch:
        # Update to or create the "pristine" branch
        synched_svnrev = None
        if not hg_switch_branch(orig_branch, svn_branch):
            return 1
    elif not hg_is_clean(svn_branch):
        if not (options.force or options.prefere2hg):
            return 1
        elif not svn_is_clean(svn_wc):
            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".

    #if options.force:
    hgsvn_rev = None
    ui.status("check sync last point", level=ui.DEBUG)
    if (synched_svnrev is None) and (not options.svn_peg):
            #since not defined peg revision, try find last synch rev
            try:
                synched  = get_svn_rev_of_hgbranch(svn_branch)
                synched_svnrev = synched.svnrev
                synched_hgrev  = synched.hgrev
            except:
                hgsvn_rev = get_svn_rev_from_hg()
                synched_svnrev = None

    if synched_svnrev is not None and synched_svnrev != 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:%s."%synched_svnrev),
                  level=ui.VERBOSE)
        run_svn(["revert", "-R", "."])
        run_svn(["up", "--ignore-externals", "-r", synched_svnrev, svn_wc])
        current_rev = synched_svnrev
        next_rev = synched_svnrev + 1

    if (synched_svnrev is None) and (options.force):
        # if forced - pull to current branch at current point
        hgsvn_rev = get_svn_rev_from_hg()

    # Reset working branch to last svn head to have a clean and linear SVN
    # history.
    heads_before = None
    if hgsvn_rev is None:
        try:
            heads_before = run_hg(["heads", "--template",
                                   "{node}%s" % os.linesep]).splitlines()
            run_hg(["update", "-C", "svn.%d" % current_rev])
        except (RunCommandError) as e:
            # on empty repo heads returns empty list
            if e.returncode != 1:
                raise

    # Load SVN log starting from current rev
    it_log_entries = iter_svn_log_entries(wc_url, next_rev, svn_greatest_rev, options.svnretry)

    #we need sure that dir state is exactly  at svn revision
    svn_revert_all()
    try:
        try:
            for log_entry in it_log_entries:
                merged = False
                if not (log_entry['merges'] is None):
                    # merges are for merge point
                    if options.merge_branches == "break":
                        ui.status("Pulled r%d is a merge of revs:%s"
                                , log_entry["revision"]
                                , log_entry['merges']
                                )
                        return 2
                    elif (options.merge_branches == "pull") or (options.merge_branches == "trypull"):
                        if svn_sync_merge_hg(
                                log_entry
                                , current_rev, svn_wc, wc_url, wc_base, svn_branch
                                , options) :
                            merged = True
                        else:
                            if options.merge_branches != "trypull":
                                return 3
                            ui.status("failed to branches of merge, so normal commit")
                    #elif options.merge_branches == "skip":

                current_rev = pull_svn_rev(log_entry, current_rev,
                                           svn_wc, wc_url, wc_base, options)
                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])
        return 0

    except KeyboardInterrupt:
        ui.status("\nStopped by user.", level=ui.ERROR)
    except ExternalCommandFailed as e:
        ui.status(str(e), level=ui.ERROR)
    except:
        ui.status("\nCommand failed with following error:\n", level=ui.ERROR)
        traceback.print_exc()


def main(argv=sys.argv):
    global subrepos

    # Defined as entry point. Must be callable without arguments.
    usage = "usage: %prog [-p SVN_PEG] [--help]"
    parser = OptionParser(usage)
    parser.add_option("-r", type="int", dest="svn_rev",
       help="limit pull up to specified revision")
    parser.add_option("-p", "--svn-peg", type="int", dest="svn_peg",
       help="SVN peg revision to locate checkout URL")
    parser.add_option("-n", "--dry-run", dest="dryrun", default=False,
                      action="store_true",
                      help="show incoming changesets without pulling them")
    parser.add_option("-f", "--force", default=False, action="store_true",
                      dest="force",
                      help=" forces go to last pulled rev and proceeds from there"
                      )
    parser.add_option("--branch", type="string", dest="svn_branch",
        help="override branch name (defaults to last path component of <SVN URL>)")
    parser.add_option("--svn-retry", dest="svnretry", default=False,
                      action="store_true",
                      help="retry SVN update command on failure")
    parser.add_option("-s", "--subrepo", dest="subrepo", type="string", default="break",
                      help="- defines operation on subrepo affected files"
                           "\n[<break>] - stops pulling if subrepo affected"
                           "\n<skip> - skips subrepo files from operations"
                     )
    parser.add_option("-a", "--noversioned_change", type="string", default="skip",
                      dest="on_noversioned_change",
                      help="=<abort> - breaks on change files that noversioned  "
                           "\n <skip> - (default) ignore this changes           ")
    parser.add_option("--merge-branches", default="break", type="string",
                      dest="merge_branches",
                      help=("\n<pull> - pull all parent braches                 "
                            "\n<trypull> - same, but forced to commit even if   "
                            "\n         parents cant be pulled fine             "
                            "\n<skip> - commit as ordinary revision             "
                            "\n<>|<break> - default:do not merge, and break pushing"
                           )
                      )
    load_hgsvn_branches_map()
    hgsubrepos = hg_subrepos();
    (options, args) = run_parser(parser, __doc__, argv)
    if args:
        display_parser_error(parser, "incorrect number of arguments")
    res = locked_main(real_main, options, args)
    return res


if __name__ == "__main__":
    sys.exit(main() or 0)
