def create_issues(target, issues, last_issue_number):
    for issue in issues:
        summary = utf8encode(issue.summary)
        description = None
        if hasattr(issue, 'description'):
            description = utf8encode(issue.description)
        group = None
        if hasattr(issue, 'permittedGroup'):
            group = utf8encode(issue.permittedGroup)
        # This loop creates and then deletes issues that don't exist in source
        # database. In other words this loop creates holes in issue numeration.
        next_number = last_issue_number + 1
        number_gap = int(issue.numberInProject) - last_issue_number - 1
        for i in range(next_number, next_number + number_gap):
            print('Creating and deleting dummy issue #%s-%d' % (issue.projectShortName, i))
            target.createIssue(issue.projectShortName, None, 'dummy', None)
            target.deleteIssue('%s-%d' % (issue.projectShortName, i))
        try:
            print('Creating issue from source issue with id %s' % issue.id)
            target.createIssue(issue.projectShortName, None, summary, description, permittedGroup=group)
        except youtrack.YouTrackException as e:
            print('Cannot create issue from source issue with id %s' % issue.id)
            print(e)
        last_issue_number = int(issue.numberInProject)
    return last_issue_number
def create_bundle_from_bundle(source, target, bundle_name, bundle_type,
                              user_importer):
    source_bundle = source.getBundle(bundle_type, bundle_name)
    # here we should check whether target YT has bundle with same name. But actually, to check tis, we should
    # get all bundles of every field type. So here we'll do a hack: just check if there is a bundle of bundle_type
    # type with this name, if there is bundle of another type -- there will be conflict, and we'll just exit with
    # corresponding message, as we can't proceed import anyway
    target_bundle_names = [
        bundle.name.strip() for bundle in target.getAllBundles(bundle_type)
    ]
    if bundle_name in target_bundle_names:
        target_bundle = target.getBundle(bundle_type, bundle_name)
        if isinstance(source_bundle, youtrack.UserBundle):
            # get users and try to import them
            user_importer.importUsersRecursively(
                set(source_bundle.get_all_users()))
            # get field and calculate not existing groups
            target_bundle_group_names = [
                elem.name.capitalize() for elem in target_bundle.groups
            ]
            groups_to_add = [
                group for group in source_bundle.groups
                if group.name.capitalize() not in target_bundle_group_names
            ]
            user_importer.importGroupsWithoutUsers(groups_to_add)
            for group in groups_to_add:
                target.addValueToBundle(target_bundle, group)
                # add individual users to bundle
            target_bundle_user_logins = [
                elem.login.capitalize()
                for elem in target_bundle.get_all_users()
            ]
            users_to_add = [
                user for user in source_bundle.users
                if user.login.capitalize() not in target_bundle_user_logins
            ]
            for user in users_to_add:
                try:
                    target.addValueToBundle(target_bundle, user)
                except youtrack.YouTrackException, e:
                    if e.response.status != 409:
                        raise e
            return
        target_value_names = [
            utf8encode(element.name).capitalize()
            for element in target_bundle.values
        ]
        for value in [
                elem for elem in source_bundle.values if utf8encode(
                    elem.name).strip().capitalize() not in target_value_names
        ]:
            try:
                target.addValueToBundle(target_bundle, value)
            except youtrack.YouTrackException, e:
                if e.response.status != 409:
                    raise e
def create_bundle_from_bundle(source, target, bundle_name, bundle_type, user_importer):
    source_bundle = source.getBundle(bundle_type, bundle_name)
    # here we should check whether target YT has bundle with same name. But actually, to check tis, we should
    # get all bundles of every field type. So here we'll do a hack: just check if there is a bundle of bundle_type
    # type with this name, if there is bundle of another type -- there will be conflict, and we'll just exit with
    # corresponding message, as we can't proceed import anyway
    target_bundle_names = [bundle.name.strip() for bundle in target.getAllBundles(bundle_type)]
    if bundle_name in target_bundle_names:
        target_bundle = target.getBundle(bundle_type, bundle_name)
        if isinstance(source_bundle, youtrack.UserBundle):
            # get users and try to import them
            user_importer.importUsersRecursively(set(source_bundle.get_all_users()))
            # get field and calculate not existing groups
            target_bundle_group_names = [elem.name.capitalize() for elem in target_bundle.groups]
            groups_to_add = [group for group in source_bundle.groups if
                             group.name.capitalize() not in target_bundle_group_names]
            user_importer.importGroupsWithoutUsers(groups_to_add)
            for group in groups_to_add:
                target.addValueToBundle(target_bundle, group)
                # add individual users to bundle
            target_bundle_user_logins = [elem.login.capitalize() for elem in target_bundle.get_all_users()]
            users_to_add = [user for user in source_bundle.users if
                            user.login.capitalize() not in target_bundle_user_logins]
            for user in users_to_add:
                try:
                    target.addValueToBundle(target_bundle, user)
                except youtrack.YouTrackException, e:
                    if e.response.status != 409:
                        raise e
            return
        target_value_names = [utf8encode(element.name).capitalize() for element in target_bundle.values]
        for value in [elem for elem in source_bundle.values if
                      utf8encode(elem.name).strip().capitalize() not in target_value_names]:
            try:
                target.addValueToBundle(target_bundle, value)
            except youtrack.YouTrackException, e:
                if e.response.status != 409:
                    raise e
def create_issues(target, issues, last_issue_number):
    for issue in issues:
        summary = utf8encode(issue.summary)
        description = None
        if hasattr(issue, 'description'):
            description = utf8encode(issue.description)
        group = None
        if hasattr(issue, 'permittedGroup'):
            group = utf8encode(issue.permittedGroup)
        # This loop creates and then deletes issues that don't exist in source
        # database. In other words this loop creates holes in issue numeration.
        next_number = last_issue_number + 1
        number_gap = int(issue.numberInProject) - last_issue_number - 1
        for i in range(next_number, next_number + number_gap):
            print 'Creating and deleting dummy issue #%s-%d' % (issue.projectShortName, i)
            target.createIssue(issue.projectShortName, None, 'dummy', None)
            target.deleteIssue('%s-%d' % (issue.projectShortName, i))
        try:
            print 'Creating issue from source issue with id %s' % issue.id
            target.createIssue(issue.projectShortName, None, summary, description, permittedGroup=group)
        except youtrack.YouTrackException, e:
            print 'Cannot create issue from source issue with id %s' % issue.id
            print e
        last_issue_number = int(issue.numberInProject)
def import_attachments_only(source_url, source_login, source_password,
                            target_url, target_login, target_password,
                            project_ids, source_token=None, target_token=None, params=None):
    if not project_ids:
        print('No projects to import. Exit...')
        return
    if params is None:
        params = {}
    start = 0
    max = 20
    source = Connection(source_url, source_login, source_password) if (source_token is None) else Connection(source_url,
                                                                                                             token=source_token)
    target = Connection(target_url, target_login, target_password) if (target_token is None) else Connection(target_url,
                                                                                                             token=target_token)

    user_importer = UserImporter(source, target, caching_users=params.get('enable_user_caching', True))
    for projectId in project_ids:
        while True:
            try:
                print('Get issues from %d to %d' % (start, start + max))
                issues = source.getIssues(projectId, '', start, max)
                if len(issues) <= 0:
                    break
                for issue in issues:
                    print('Process attachments for issue %s' % issue.id)
                    existing_attachments = dict()
                    try:
                        for a in target.getAttachments(issue.id):
                            existing_attachments[a.name + '\n' + a.created] = a
                    except youtrack.YouTrackException as e:
                        if e.response.status == 404:
                            print("Skip importing attachments because issue %s doesn't exist" % issue.id)
                            continue
                        raise e

                    attachments = []

                    users = set([])
                    for a in issue.getAttachments():
                        if a.name + '\n' + a.created in existing_attachments and not params.get('replace_attachments'):
                            print("Skip attachment '%s' (created: %s) because it's already exists" %
                                  (utf8encode(a.name), utf8encode(a.created)))
                            continue
                        attachments.append(a)
                        author = a.getAuthor()
                        if author is not None:
                            users.add(author)
                    user_importer.importUsersRecursively(users)

                    for a in attachments:
                        print('Transfer attachment of %s: %s' % (utf8encode(issue.id), utf8encode(a.name)))
                        try:
                            target.createAttachmentFromAttachment(issue.id, a)
                        except BaseException as e:
                            print('Cannot import attachment [ %s ]' % utf8encode(a.name))
                            print(repr(e))
                            continue
                        if params.get('replace_attachments'):
                            try:
                                old_attachment = existing_attachments.get(a.name + '\n' + a.created)
                                if old_attachment:
                                    print('Deleting old attachment')
                                    target.deleteAttachment(issue.id, old_attachment.id)
                            except BaseException as e:
                                print("Cannot delete attachment '%s' from issue %s" % (
                                utf8encode(a.name), utf8encode(issue.id)))
                                print(e)
            except Exception as e:
                print('Cannot process issues from %d to %d' % (start, start + max))
                traceback.print_exc()
                raise e
            start += max
def youtrack2youtrack(source_url, source_login, source_password, target_url, target_login, target_password,
                      project_ids, query='', source_token=None, target_token=None, params=None):
    if not len(project_ids):
        print("You should sign at least one project to import")
        return
    if params is None:
        params = {}

    source = Connection(source_url, source_login, source_password) if (source_token is None) else Connection(source_url,
                                                                                                             token=source_token)
    target = Connection(target_url, target_login, target_password) if (target_token is None) else Connection(target_url,
                                                                                                             token=target_token)
    # , proxy_info = httplib2.ProxyInfo(socks.PROXY_TYPE_HTTP, 'localhost', 8888)

    print("Import issue link types")
    for ilt in source.getIssueLinkTypes():
        try:
            print(target.createIssueLinkType(ilt))
        except youtrack.YouTrackException as e:
            print(e.message)

    user_importer = UserImporter(source, target, caching_users=params.get('enable_user_caching', True))
    link_importer = LinkImporter(target)

    # create all projects with minimum info and project lead set
    created_projects = []
    for project_id in project_ids:
        created = create_project_stub(source, target, project_id, user_importer)
        created_projects.append(created)

    # save created project ids to create correct group roles afterwards
    user_importer.addCreatedProjects([project.id for project in created_projects])
    # import project leads with group they are included and roles assigned to these groups
    user_importer.importUsersRecursively([target.getUser(project.lead) for project in created_projects])
    # afterwards in a script any user import imply recursive import

    cf_names_to_import = set([])  # names of cf prototypes that should be imported
    for project_id in project_ids:
        cf_names_to_import.update([pcf.name.capitalize() for pcf in source.getProjectCustomFields(project_id)])

    target_cf_names = [pcf.name.capitalize() for pcf in target.getCustomFields()]

    period_cf_names = []

    for cf_name in cf_names_to_import:
        source_cf = source.getCustomField(cf_name)
        if source_cf.type.lower() == 'period':
            period_cf_names.append(source_cf.name.lower())

        print("Processing custom field '%s'" % utf8encode(cf_name))
        if cf_name in target_cf_names:
            target_cf = target.getCustomField(cf_name)
            if not (target_cf.type == source_cf.type):
                print("In your target and source YT instances you have field with name [ %s ]" % utf8encode(cf_name))
                print("They have different types. Source field type [ %s ]. Target field type [ %s ]" %
                      (source_cf.type, target_cf.type))
                print("exiting...")
                exit()
        else:
            if hasattr(source_cf, "defaultBundle"):
                create_bundle_from_bundle(source, target, source_cf.defaultBundle, source_cf.type, user_importer)
            target.createCustomField(source_cf)

    failed_commands = []

    for projectId in project_ids:
        source = Connection(source_url, source_login, source_password) if (source_token is None) else Connection(
            source_url, token=source_token)
        target = Connection(target_url, target_login, target_password) if (target_token is None) else Connection(
            target_url, token=target_token)
        # , proxy_info = httplib2.ProxyInfo(socks.PROXY_TYPE_HTTP, 'localhost', 8888)
        # reset connections to avoid disconnections
        user_importer.resetConnections(source, target)
        link_importer.resetConnections(target)

        # copy project, subsystems, versions
        project = source.getProject(projectId)

        link_importer.addAvailableIssuesFrom(projectId)
        project_custom_fields = source.getProjectCustomFields(projectId)
        # create bundles and additional values
        for pcf_ref in project_custom_fields:
            pcf = source.getProjectCustomField(projectId, pcf_ref.name)
            if hasattr(pcf, "bundle"):
                try:
                    create_bundle_from_bundle(source, target, pcf.bundle, source.getCustomField(pcf.name).type,
                                              user_importer)
                except youtrack.YouTrackException as e:
                    if e.response.status != 409:
                        raise e
                    else:
                        print(e)

        target_project_fields = [pcf.name.lower() for pcf in target.getProjectCustomFields(projectId)]
        for field in project_custom_fields:
            if field.name.lower() in target_project_fields:
                if hasattr(field, 'bundle'):
                    if field.bundle != target.getProjectCustomField(projectId, field.name).bundle:
                        target.deleteProjectCustomField(projectId, field.name)
                        create_project_custom_field(target, field, projectId)
            else:
                try:
                    create_project_custom_field(target, field, projectId)
                except youtrack.YouTrackException as e:
                    if e.response.status != 409:
                        raise e
                    else:
                        print(e)

        # copy issues
        start = 0
        max = 20

        sync_workitems = enable_time_tracking(source, target, projectId)
        tt_settings = target.getProjectTimeTrackingSettings(projectId)

        print("Import issues")
        last_created_issue_number = 0

        while True:
            try:
                print("Get issues from " + str(start) + " to " + str(start + max))
                issues = source.getIssues(projectId, query, start, max)

                if len(issues) <= 0:
                    break

                if convert_period_values and period_cf_names:
                    for issue in issues:
                        for pname in period_cf_names:
                            for fname in issue.__dict__:
                                if fname.lower() != pname:
                                    continue
                                issue[fname] = period_to_minutes(issue[fname])

                users = set([])

                for issue in issues:
                    print("Collect users for issue [%s]" % issue.id)

                    users.add(issue.getReporter())
                    if issue.hasAssignee():
                        if isinstance(issue.Assignee, (list, tuple)):
                            users.update(issue.getAssignee())
                        else:
                            users.add(issue.getAssignee())
                    # TODO: http://youtrack.jetbrains.net/issue/JT-6100
                    users.add(issue.getUpdater())
                    if issue.hasVoters():
                        users.update(issue.getVoters())
                    for comment in issue.getComments():
                        users.add(comment.getAuthor())

                    print("Collect links for issue [%s]" % issue.id)
                    link_importer.collectLinks(issue.getLinks(True))
                    # links.extend(issue.getLinks(True))

                    # fix problem with comment.text
                    for comment in issue.getComments():
                        if not hasattr(comment, "text") or (len(comment.text.strip()) == 0):
                            setattr(comment, 'text', 'no text')

                user_importer.importUsersRecursively(users)

                print("Create issues [" + str(len(issues)) + "]")
                if params.get('create_new_issues'):
                    create_issues(target, issues, last_created_issue_number)
                else:
                    print(target.importIssues(projectId, project.name + ' Assignees', issues))
                link_importer.addAvailableIssues(issues)

                for issue in issues:
                    try:
                        target_issue = target.getIssue(issue.id)
                    except youtrack.YouTrackException as e:
                        print("Cannot get target issue")
                        print(e)
                        continue

                    if params.get('sync_tags') and issue.tags:
                        try:
                            for tag in issue.tags:
                                tag = re.sub(r'[,&<>]', '_', tag)
                                try:
                                    target.executeCommand(issue.id, 'tag ' + tag, disable_notifications=True)
                                except youtrack.YouTrackException:
                                    tag = re.sub(r'[\s-]', '_', tag)
                                    target.executeCommand(issue.id, 'tag ' + tag, disable_notifications=True)
                        except youtrack.YouTrackException as e:
                            print("Cannot sync tags for issue " + issue.id)
                            print(e)

                    if params.get('add_new_comments'):
                        target_comments = dict()
                        max_id = 0
                        for c in target_issue.getComments():
                            target_comments[c.created] = c
                            if max_id < c.created:
                                max_id = c.created
                        for c in issue.getComments():
                            if c.created > max_id or c.created not in target_comments:
                                group = None
                                if hasattr(c, 'permittedGroup'):
                                    group = c.permittedGroup
                                try:
                                    target.executeCommand(issue.id, 'comment', c.text, group, c.author,
                                                          disable_notifications=True)
                                except youtrack.YouTrackException as e:
                                    print('Cannot add comment to issue')
                                    print(e)

                    if params.get('sync_custom_fields'):
                        skip_fields = []
                        if tt_settings and tt_settings.Enabled and tt_settings.TimeSpentField:
                            skip_fields.append(tt_settings.TimeSpentField)
                        skip_fields = [name.lower() for name in skip_fields]
                        for pcf in [pcf for pcf in project_custom_fields if pcf.name.lower() not in skip_fields]:
                            target_cf_value = None
                            if pcf.name in target_issue:
                                target_cf_value = target_issue[pcf.name]
                                if isinstance(target_cf_value, (list, tuple)):
                                    target_cf_value = set(target_cf_value)
                                elif target_cf_value == target.getProjectCustomField(projectId, pcf.name).emptyText:
                                    target_cf_value = None
                            source_cf_value = None
                            if pcf.name in issue:
                                source_cf_value = issue[pcf.name]
                                if isinstance(source_cf_value, (list, tuple)):
                                    source_cf_value = set(source_cf_value)
                                elif source_cf_value == source.getProjectCustomField(projectId, pcf.name).emptyText:
                                    source_cf_value = None
                            if source_cf_value == target_cf_value:
                                continue
                            if isinstance(source_cf_value, set) or isinstance(target_cf_value, set):
                                if source_cf_value is None:
                                    source_cf_value = set([])
                                elif not isinstance(source_cf_value, set):
                                    source_cf_value = set([source_cf_value])
                                if target_cf_value is None:
                                    target_cf_value = set([])
                                elif not isinstance(target_cf_value, set):
                                    target_cf_value = set([target_cf_value])
                                for v in target_cf_value:
                                    if v not in source_cf_value:
                                        target.executeCommand(issue.id, 'remove %s %s' % (pcf.name, v),
                                                              disable_notifications=True)
                                for v in source_cf_value:
                                    if v not in target_cf_value:
                                        target.executeCommand(issue.id, 'add %s %s' % (pcf.name, v),
                                                              disable_notifications=True)
                            else:
                                if source_cf_value is None:
                                    source_cf_value = target.getProjectCustomField(projectId, pcf.name).emptyText
                                if pcf.type.lower() == 'date':
                                    m = re.match(r'(\d{10})(?:\d{3})?', str(source_cf_value))
                                    if m:
                                        source_cf_value = datetime.datetime.fromtimestamp(
                                            int(m.group(1))).strftime('%Y-%m-%d')
                                elif pcf.type.lower() == 'period':
                                    source_cf_value = '%sm' % source_cf_value
                                command = '%s %s' % (pcf.name, source_cf_value)
                                try:
                                    target.executeCommand(issue.id, command, disable_notifications=True)
                                except youtrack.YouTrackException as e:
                                    if e.response.status == 412 and e.response.reason.find('Precondition Failed') > -1:
                                        print('WARN: Some workflow blocks following command: %s' % command)
                                        failed_commands.append((issue.id, command))

                    if sync_workitems:
                        workitems = source.getWorkItems(issue.id)
                        if workitems:
                            existing_workitems = dict()
                            target_workitems = target.getWorkItems(issue.id)
                            if target_workitems:
                                for w in target_workitems:
                                    _id = '%s\n%s\n%s' % (w.date, w.authorLogin, w.duration)
                                    if hasattr(w, 'description'):
                                        _id += '\n%s' % w.description
                                    existing_workitems[_id] = w
                            new_workitems = []
                            for w in workitems:
                                _id = '%s\n%s\n%s' % (w.date, w.authorLogin, w.duration)
                                if hasattr(w, 'description'):
                                    _id += '\n%s' % w.description
                                if _id not in existing_workitems:
                                    new_workitems.append(w)
                            if new_workitems:
                                print("Process workitems for issue [ " + issue.id + "]")
                                try:
                                    user_importer.importUsersRecursively(
                                        [source.getUser(w.authorLogin)
                                         for w in new_workitems])
                                    target.importWorkItems(issue.id, new_workitems)
                                except youtrack.YouTrackException as e:
                                    if e.response.status == 404:
                                        print("WARN: Target YouTrack doesn't support workitems importing.")
                                        print("WARN: Workitems won't be imported.")
                                        sync_workitems = False
                                    else:
                                        print("ERROR: Skipping workitems because of error:" + str(e))

                    print("Process attachments for issue [%s]" % issue.id)
                    existing_attachments = dict()
                    try:
                        for a in target.getAttachments(issue.id):
                            existing_attachments[a.name + '\n' + a.created] = a
                    except youtrack.YouTrackException as e:
                        if e.response.status == 404:
                            print("Skip importing attachments because issue %s doesn't exist" % issue.id)
                            continue
                        raise e

                    attachments = []

                    users = set([])
                    for a in issue.getAttachments():
                        if a.name + '\n' + a.created in existing_attachments and not params.get('replace_attachments'):
                            a.name = utf8encode(a.name)
                            try:
                                print("Skip attachment '%s' (created: %s) because it's already exists"
                                      % (utf8encode(a.name), utf8encode(a.created)))
                            except Exception:
                                pass
                            continue
                        attachments.append(a)
                        author = a.getAuthor()
                        if author is not None:
                            users.add(author)
                    user_importer.importUsersRecursively(users)

                    for a in attachments:
                        print("Transfer attachment of " + utf8encode(issue.id) + ": " + utf8encode(a.name))
                        # TODO: add authorLogin to workaround http://youtrack.jetbrains.net/issue/JT-6082
                        # a.authorLogin = target_login
                        try:
                            target.createAttachmentFromAttachment(issue.id, a)
                        except BaseException as e:
                            print("Cant import attachment [ %s ]" % utf8encode(a.name))
                            print(repr(e))
                            continue
                        if params.get('replace_attachments'):
                            try:
                                old_attachment = existing_attachments.get(a.name + '\n' + a.created)
                                if old_attachment:
                                    print('Deleting old attachment')
                                    target.deleteAttachment(issue.id, old_attachment.id)
                            except BaseException as e:
                                print("Cannot delete attachment '%s' from issue %s" % (
                                utf8encode(a.name), utf8encode(issue.id)))
                                print(e)

            except Exception as e:
                print('Cant process issues from ' + str(start) + ' to ' + str(start + max))
                traceback.print_exc()
                raise e

            start += max

    print("Import issue links")
    link_importer.importCollectedLinks()

    print("Trying to execute failed commands once again")
    for issue_id, command in failed_commands:
        try:
            print('Executing command on issue %s: %s' % (issue_id, command))
            target.executeCommand(issue_id, command, disable_notifications=True)
        except youtrack.YouTrackException as e:
            print('Failed to execute command for issue #%s: %s' % (issue_id, command))
            print(e)
Example #7
0
def bugzilla2youtrack(params):
    # Connecting to Bugzilla
    client = Client(host=params['bz_host'],
                    port=int(params['bz_port']),
                    login=params['bz_login'],
                    password=params['bz_password'],
                    db_name=params['bz_db'])

    bz_product_names = params.get('bz_product_names')
    if not bz_product_names:
        answer = raw_input(
            "All projects will be imported. Are you sure? [Y/n] ")
        if answer.strip().lower() not in ("y", "yes", ""):
            sys.exit()
        bz_product_names = client.get_product_names()

    print("bz_product_names: " + repr(bz_product_names))

    # Connecting to YouTrack
    token = params.get('token')
    if not token and 'token_file' in params:
        try:
            with open(params['token_file'], 'r') as f:
                token = f.read().strip()
        except (OSError, IOError) as e:
            print("Cannot load token from file: " + str(e))
            sys.exit(1)
    if token:
        target = Connection(params['yt_url'], token=token)
    elif 'yt_login' in params:
        target = Connection(params['yt_url'], params.get('yt_login'),
                            params.get('yt_password'))
    else:
        print("You have to provide token or login/password to import data")
        sys.exit(1)

    print("Creating issue link types")
    link_types = client.get_issue_link_types()
    for link in link_types:
        print("Processing link type [ %s ]" % link.name)
        try:
            target.createIssueLinkType(to_yt_issue_link_type(link))
        except YouTrackException:
            print(
                "Can't create link type [ %s ] (maybe because it already exists)"
                % link.name)
    print("Creating issue link types finished")

    print("Creating custom fields")
    custom_fields = client.get_custom_fields()
    for cf in custom_fields:
        create_yt_custom_field(cf, target)
    print("Creating custom fields finished")

    for key in youtrackutils.bugzilla.FIELD_TYPES:
        if key not in youtrack.EXISTING_FIELDS:
            create_custom_field(target,
                                youtrackutils.bugzilla.FIELD_TYPES[key],
                                key,
                                True,
                                bundle_policy="1")

    bz_product_ids = []

    for name in bz_product_names:
        product_id = str(client.get_product_id_by_name(name))
        bz_product_ids.append(product_id)
        print("Creating project [ %s ] with name [ %s ]" % (product_id, name))
        try:
            target.getProject(product_id)
        except YouTrackException:
            target.createProjectDetailed(
                product_id, name, client.get_project_description(product_id),
                'root')

        print("Importing components for project [ %s ]" % product_id)
        process_components(client.get_components(product_id), product_id,
                           target)
        print("Importing components finished for project [ %s ]" % product_id)

        print("Importing versions for project [ %s ]" % product_id)
        process_versions(client.get_versions(product_id), product_id, target)
        print("Importing versions finished for project [ %s ] finished" %
              product_id)

        print("Importing issues to project [ %s ]" % product_id)
        max_count = 100
        count = 0
        from_id = 0
        bz_issues_count = client.get_issues_count(product_id)
        while count < bz_issues_count:
            batch = client.get_issues(product_id, from_id, from_id + max_count)
            batch = [bz_issue for bz_issue in batch]
            count += len(batch)
            from_id += max_count
            target.importIssues(product_id, product_id + " assignees", [
                to_yt_issue(bz_issue, product_id, target) for bz_issue in batch
            ])
            # todo convert to good tags import
            for issue in batch:
                tags = issue["keywords"] | issue["flags"]
                for t in tags:
                    print("Processing tag [ %s ]" % t.encode('utf8'))
                    target.executeCommand(
                        str(product_id) + "-" +
                        str(issue[get_number_in_project_field_name()]),
                        "tag " + t.encode('utf8'))
            for issue in batch:
                issue_id = str(product_id) + '-' + \
                           str(issue[get_number_in_project_field_name()])
                for attach in issue["attachments"]:
                    print("Processing attachment [ %s ] for issue %s" %
                          (utf8encode(attach.name), issue_id))
                    content = StringIO(attach.content)
                    try:
                        target.importAttachment(
                            issue_id, attach.name, content,
                            attach.reporter.login, None, None,
                            str(int(attach.created) * 1000))
                    except urllib2.HTTPError as e:
                        print("WARN: Cant import attachment [ %s ]" %
                              utf8encode(attach.name))
                        print(e.code)
                        print(e.read())
                        print("Please check Max Upload File Size in YouTrack")
                        continue
        print("Importing issues to project [ %s ] finished" % product_id)

    # todo add pagination to links
    print("Importing issue links")
    cf_links = client.get_issue_links()
    duplicate_links = client.get_duplicate_links()
    if len(duplicate_links):
        try:
            target.createIssueLinkTypeDetailed("Duplicate", "duplicates",
                                               "is duplicated by", True)
        except YouTrackException:
            print(
                "Can't create link type [ Duplicate ] (maybe because it already exists)"
            )
    depend_links = client.get_dependencies_link()
    if len(depend_links):
        try:
            target.createIssueLinkTypeDetailed("Depend", "depends on",
                                               "is required for", True)
        except YouTrackException:
            print(
                "Can't create link type [ Depend ] (maybe because it already exists)"
            )
    links = cf_links | duplicate_links | depend_links

    links_to_import = list([])
    for link in links:
        print("Processing link %s for issue%s" % (link.name, link.source))
        if (str(link.target_product_id) in bz_product_ids) and (str(
                link.source_product_id) in bz_product_ids):
            links_to_import.append(to_yt_issue_link(link))
    print(target.importLinks(links_to_import))
    print("Importing issue links finished")
def import_attachments_only(source_url, source_login, source_password,
                            target_url, target_login, target_password,
                            project_ids, params=None):
    if not project_ids:
        print 'No projects to import. Exit...'
        return
    if params is None:
        params = {}
    start = 0
    max = 20
    source = Connection(source_url, source_login, source_password)
    target = Connection(target_url, target_login, target_password)
    user_importer = UserImporter(source, target, caching_users=params.get('enable_user_caching', True))
    for projectId in project_ids:
        while True:
            try:
                print 'Get issues from %d to %d' % (start, start + max)
                issues = source.getIssues(projectId, '', start, max)
                if len(issues) <= 0:
                    break
                for issue in issues:
                    print 'Process attachments for issue %s' % issue.id
                    existing_attachments = dict()
                    try:
                        for a in target.getAttachments(issue.id):
                            existing_attachments[a.name + '\n' + a.created] = a
                    except youtrack.YouTrackException, e:
                        if e.response.status == 404:
                            print "Skip importing attachments because issue %s doesn't exist" % issue.id
                            continue
                        raise e

                    attachments = []

                    users = set([])
                    for a in issue.getAttachments():
                        if a.name + '\n' + a.created in existing_attachments and not params.get('replace_attachments'):
                            print "Skip attachment '%s' (created: %s) because it's already exists" \
                                  % (utf8encode(a.name), utf8encode(a.created))
                            continue
                        attachments.append(a)
                        author = a.getAuthor()
                        if author is not None:
                            users.add(author)
                    user_importer.importUsersRecursively(users)

                    for a in attachments:
                        print 'Transfer attachment of %s: %s' % (utf8encode(issue.id), utf8encode(a.name))
                        try:
                            target.createAttachmentFromAttachment(issue.id, a)
                        except BaseException, e:
                            print 'Cannot import attachment [ %s ]' % utf8encode(a.name)
                            print repr(e)
                            continue
                        if params.get('replace_attachments'):
                            try:
                                old_attachment = existing_attachments.get(a.name + '\n' + a.created)
                                if old_attachment:
                                    print 'Deleting old attachment'
                                    target.deleteAttachment(issue.id, old_attachment.id)
                            except BaseException, e:
                                print "Cannot delete attachment '%s' from issue %s" % (utf8encode(a.name), utf8encode(issue.id))
                                print e
    #afterwards in a script any user import imply recursive import

    cf_names_to_import = set([]) # names of cf prototypes that should be imported
    for project_id in project_ids:
        cf_names_to_import.update([pcf.name.capitalize() for pcf in source.getProjectCustomFields(project_id)])

    target_cf_names = [pcf.name.capitalize() for pcf in target.getCustomFields()]

    period_cf_names = []

    for cf_name in cf_names_to_import:
        source_cf = source.getCustomField(cf_name)
        if source_cf.type.lower() == 'period':
            period_cf_names.append(source_cf.name.lower())

        print "Processing custom field '%s'" % utf8encode(cf_name)
        if cf_name in target_cf_names:
            target_cf = target.getCustomField(cf_name)
            if not(target_cf.type == source_cf.type):
                print "In your target and source YT instances you have field with name [ %s ]" % utf8encode(cf_name)
                print "They have different types. Source field type [ %s ]. Target field type [ %s ]" %\
                      (source_cf.type, target_cf.type)
                print "exiting..."
                exit()
        else:
            if hasattr(source_cf, "defaultBundle"):
                create_bundle_from_bundle(source, target, source_cf.defaultBundle, source_cf.type, user_importer)
            target.createCustomField(source_cf)

    failed_commands = []
            pcf.name.capitalize()
            for pcf in source.getProjectCustomFields(project_id)
        ])

    target_cf_names = [
        pcf.name.capitalize() for pcf in target.getCustomFields()
    ]

    period_cf_names = []

    for cf_name in cf_names_to_import:
        source_cf = source.getCustomField(cf_name)
        if source_cf.type.lower() == 'period':
            period_cf_names.append(source_cf.name.lower())

        print "Processing custom field '%s'" % utf8encode(cf_name)
        if cf_name in target_cf_names:
            target_cf = target.getCustomField(cf_name)
            if not (target_cf.type == source_cf.type):
                print "In your target and source YT instances you have field with name [ %s ]" % utf8encode(
                    cf_name)
                print "They have different types. Source field type [ %s ]. Target field type [ %s ]" %\
                      (source_cf.type, target_cf.type)
                print "exiting..."
                exit()
        else:
            if hasattr(source_cf, "defaultBundle"):
                create_bundle_from_bundle(source, target,
                                          source_cf.defaultBundle,
                                          source_cf.type, user_importer)
            target.createCustomField(source_cf)