gpt4 book ai didi

import - 如何将 Jira 问题导出到 BitBucket

转载 作者:行者123 更新时间:2023-12-01 09:00:18 29 4
gpt4 key购买 nike

我刚刚将我的项目代码从 java.net 移到了 BitBucket。但是我的 jira 问题跟踪仍然托管在 java.net 上,尽管 BitBucket 确实有一些选项可以链接到外部问题跟踪器,但我认为我不能将它用于 java.net,尤其是因为我没有管理员权限需要安装 DVCS 连接器。

所以我认为另一种选择是导出然后将问题导入 BitBucket 问题跟踪器,这可能吗?

到目前为止的进展
所以我尝试按照下面使用 OSX 的两个信息丰富的答案中的步骤进行操作,但我遇到了一个问题 - 我对脚本实际调用的内容感到困惑,因为在答案中它谈到了 export.py 但不存在具有该名称的脚本所以我重命名了我下载的那个。

  • 须藤easy_install pip (OSX)
  • pip 安装 jira
  • pip 安装配置解析器
  • easy_install -U 安装工具
  • 转至 https://bitbucket.org/reece/rcore ,选择下载选项卡,下载 zip 并解压缩,然后重命名为 reece(由于某种原因 git clone https://bitbucket.org/reece/rcore 失败并出现错误)
  • cd reece/rcore
  • 在 rcore 子文件夹中将脚本另存为 export.py
  • 用 import.py 中的项目替换 iteritem
  • 将 iteritems 替换为 types/immutabledict.py
  • 在 rcore 文件夹中创建 .config
  • 创建 .config/jira-issues-move-to-bitbucket.conf 包含

    jira-用户名=paultaylor

    jira-hostname= https://java.net/jira/browse/JAUDIOTAGGER

    jira-password=密码
  • 运行 python export.py --jira-project jaudiotagger


  • macbook:rcore paul$ python export.py --jira-project jaudiotagger
    Traceback (most recent call last):
    File "export.py", line 24, in <module>
    import configparser
    ImportError: No module named configparser
    - Run python export.py --jira-project jaudiotagger

    我需要以 root 身份运行 pip insdtall
  • 须藤 pip 安装 configparser

  • 这有效

    但现在
  • python export.py --jira.project jaudiotagger


  • File "export.py" line 35, in <module?
    from jira.client import JIRA
    ImportError: No module named jira.client

    最佳答案

    您可以将问题导入 BitBucket,它们只需要在 appropriate format 中.幸运的是,Reece Hart 已经written a Python script连接到 Jira 实例并导出问题。

    为了让脚本运行,我必须安装 Jira Python package以及最新版本的rcore (如果你使用 pip,你会得到一个不兼容的先前版本,所以你必须获取源代码)。我还必须替换 iteritems 的所有实例与 items在脚本和 rcore/types/immutabledict.py 中使其适用于 Python 3。您还需要使用您的项目使用的值填写字典( priority_mapperson_map 等)。最后,您需要一个包含连接信息的配置文件(请参阅脚本顶部的注释)。

    基本的命令行用法是 export.py --jira-project <project>
    导出数据后,请参阅 instructions for importing issues to BitBucket

    #!/usr/bin/env python

    """extract issues from JIRA and export to a bitbucket archive

    See:
    https://confluence.atlassian.com/pages/viewpage.action?pageId=330796872
    https://confluence.atlassian.com/display/BITBUCKET/Mark+up+comments
    https://bitbucket.org/tutorials/markdowndemo/overview

    2014-04-12 08:26 Reece Hart <reecehart@gmail.com>


    Requires a file ~/.config/jira-issues-move-to-bitbucket.conf
    with content like
    [default]
    jira-username=some.user
    jira-hostname=somewhere.jira.com
    jira-password=ur$pass

    """

    import argparse
    import collections
    import configparser
    import glob
    import itertools
    import json
    import logging
    import os
    import pprint
    import re
    import sys
    import zipfile

    from jira.client import JIRA

    from rcore.types.immutabledict import ImmutableDict


    priority_map = {
    'Critical (P1)': 'critical',
    'Major (P2)': 'major',
    'Minor (P3)': 'minor',
    'Nice (P4)': 'trivial',
    }
    person_map = {
    'reece.hart': 'reece',
    # etc
    }
    issuetype_map = {
    'Improvement': 'enhancement',
    'New Feature': 'enhancement',
    'Bug': 'bug',
    'Technical task': 'task',
    'Task': 'task',
    }
    status_map = {
    'Closed': 'resolved',
    'Duplicate': 'duplicate',
    'In Progress': 'open',
    'Open': 'new',
    'Reopened': 'open',
    'Resolved': 'resolved',
    }



    def parse_args(argv):
    def sep_and_flatten(l):
    # split comma-sep elements and flatten list
    # e.g., ['a','b','c,d'] -> set('a','b','c','d')
    return list( itertools.chain.from_iterable(e.split(',') for e in l) )

    cf = configparser.ConfigParser()
    cf.readfp(open(os.path.expanduser('~/.config/jira-issues-move-to-bitbucket.conf'),'r'))

    ap = argparse.ArgumentParser(
    description = __doc__
    )

    ap.add_argument(
    '--jira-hostname', '-H',
    default = cf.get('default','jira-hostname',fallback=None),
    help = 'host name of Jira instances (used for url like https://hostname/, e.g., "instancename.jira.com")',
    )
    ap.add_argument(
    '--jira-username', '-u',
    default = cf.get('default','jira-username',fallback=None),
    )
    ap.add_argument(
    '--jira-password', '-p',
    default = cf.get('default','jira-password',fallback=None),
    )
    ap.add_argument(
    '--jira-project', '-j',
    required = True,
    help = 'project key (e.g., JRA)',
    )
    ap.add_argument(
    '--jira-issues', '-i',
    action = 'append',
    default = [],
    help = 'issue id (e.g., JRA-9); multiple and comma-separated okay; default = all in project',
    )
    ap.add_argument(
    '--jira-issues-file', '-I',
    help = 'file containing issue ids (e.g., JRA-9)'
    )
    ap.add_argument(
    '--jira-components', '-c',
    action = 'append',
    default = [],
    help = 'components criterion; multiple and comma-separated okay; default = all in project',
    )
    ap.add_argument(
    '--existing', '-e',
    action = 'store_true',
    default = False,
    help = 'read existing archive (from export) and merge new issues'
    )

    opts = ap.parse_args(argv)

    opts.jira_components = sep_and_flatten(opts.jira_components)
    opts.jira_issues = sep_and_flatten(opts.jira_issues)

    return opts


    def link(url,text=None):
    return "[{text}]({url})".format(url=url,text=url if text is None else text)

    def reformat_to_markdown(desc):
    def _indent4(mo):
    i = " "
    return i + mo.group(1).replace("\n",i)
    def _repl_mention(mo):
    return "@" + person_map[mo.group(1)]
    #desc = desc.replace("\r","")
    desc = re.sub("{noformat}(.+?){noformat}",_indent4,desc,flags=re.DOTALL+re.MULTILINE)
    desc = re.sub(opts.jira_project+r"-(\d+)",r"issue #\1",desc)
    desc = re.sub(r"\[~([^]]+)\]",_repl_mention,desc)
    return desc

    def fetch_issues(opts,jcl):
    jql = [ 'project = ' + opts.jira_project ]
    if opts.jira_components:
    jql += [ ' OR '.join([ 'component = '+c for c in opts.jira_components ]) ]
    if opts.jira_issues:
    jql += [ ' OR '.join([ 'issue = '+i for i in opts.jira_issues ]) ]
    jql_str = ' AND '.join(["("+q+")" for q in jql])
    logging.info('executing query ' + jql_str)
    return jcl.search_issues(jql_str,maxResults=500)


    def jira_issue_to_bb_issue(opts,jcl,ji):
    """convert a jira issue to a dictionary with values appropriate for
    POSTing as a bitbucket issue"""
    logger = logging.getLogger(__name__)

    content = reformat_to_markdown(ji.fields.description) if ji.fields.description else ''

    if ji.fields.assignee is None:
    resp = None
    else:
    resp = person_map[ji.fields.assignee.name]

    reporter = person_map[ji.fields.reporter.name]

    jiw = jcl.watchers(ji.key)
    watchers = [ person_map[u.name] for u in jiw.watchers ] if jiw else []

    milestone = None
    if ji.fields.fixVersions:
    vnames = [ v.name for v in ji.fields.fixVersions ]
    milestone = vnames[0]
    if len(vnames) > 1:
    logger.warn("{ji.key}: bitbucket issues may have only 1 milestone (JIRA fixVersion); using only first ({f}) and ignoring rest ({r})".format(
    ji=ji, f=milestone, r=",".join(vnames[1:])))

    issue_id = extract_issue_number(ji.key)

    bbi = {
    'status': status_map[ji.fields.status.name],
    'priority': priority_map[ji.fields.priority.name],
    'kind': issuetype_map[ji.fields.issuetype.name],
    'content_updated_on': ji.fields.created,
    'voters': [],
    'title': ji.fields.summary,
    'reporter': reporter,
    'component': None,
    'watchers': watchers,
    'content': content,
    'assignee': resp,
    'created_on': ji.fields.created,
    'version': None, # ?
    'edited_on': None,
    'milestone': milestone,
    'updated_on': ji.fields.updated,
    'id': issue_id,
    }

    return bbi


    def jira_comment_to_bb_comment(opts,jcl,jc):
    bbc = {
    'content': reformat_to_markdown(jc.body),
    'created_on': jc.created,
    'id': int(jc.id),
    'updated_on': jc.updated,
    'user': person_map[jc.author.name],
    }
    return bbc

    def extract_issue_number(jira_issue_key):
    return int(jira_issue_key.split('-')[-1])
    def jira_key_to_bb_issue_tag(jira_issue_key):
    return 'issue #' + str(extract_issue_number(jira_issue_key))

    def jira_link_text(jk):
    return link("https://invitae.jira.com/browse/"+jk,jk) + " (Invitae access required)"


    if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger(__name__)


    opts = parse_args(sys.argv[1:])

    dir_name = opts.jira_project
    if opts.jira_components:
    dir_name += '-' + ','.join(opts.jira_components)

    if opts.jira_issues_file:
    issues = [i.strip() for i in open(opts.jira_issues_file,'r')]
    logger.info("added {n} issues from {opts.jira_issues_file} to issues list".format(n=len(issues),opts=opts))
    opts.jira_issues += issues

    opts.dir = os.path.join('/','tmp',dir_name)
    opts.att_rel_dir = 'attachments'
    opts.att_abs_dir = os.path.join(opts.dir,opts.att_rel_dir)
    opts.json_fn = os.path.join(opts.dir,'db-1.0.json')
    if not os.path.isdir(opts.att_abs_dir):
    os.makedirs(opts.att_abs_dir)

    opts.jira_issues = list(set(opts.jira_issues)) # distinctify

    jcl = JIRA({'server': 'https://{opts.jira_hostname}/'.format(opts=opts)},
    basic_auth=(opts.jira_username,opts.jira_password))


    if opts.existing:
    issues_db = json.load(open(opts.json_fn,'r'))
    existing_ids = [ i['id'] for i in issues_db['issues'] ]
    logger.info("read {n} issues from {fn}".format(n=len(existing_ids),fn=opts.json_fn))
    else:
    issues_db = dict()
    issues_db['meta'] = {
    'default_milestone': None,
    'default_assignee': None,
    'default_kind': "bug",
    'default_component': None,
    'default_version': None,
    }
    issues_db['attachments'] = []
    issues_db['comments'] = []
    issues_db['issues'] = []
    issues_db['logs'] = []

    issues_db['components'] = [ {'name':v.name} for v in jcl.project_components(opts.jira_project) ]
    issues_db['milestones'] = [ {'name':v.name} for v in jcl.project_versions(opts.jira_project) ]
    issues_db['versions'] = issues_db['milestones']


    # bb_issue_map: bb issue # -> bitbucket issue
    bb_issue_map = ImmutableDict( (i['id'],i) for i in issues_db['issues'] )

    # jk_issue_map: jira key -> bitbucket issue
    # contains only items migrated from JIRA (i.e., not preexisting issues with --existing)
    jk_issue_map = ImmutableDict()

    # issue_links is a dict of dicts of lists, using JIRA keys
    # e.g., links['CORE-135']['depends on'] = ['CORE-137']
    issue_links = collections.defaultdict(lambda: collections.defaultdict(lambda: []))


    issues = fetch_issues(opts,jcl)
    logger.info("fetch {n} issues from JIRA".format(n=len(issues)))
    for ji in issues:
    # Pfft. Need to fetch the issue again due to bug in JIRA.
    # See https://bitbucket.org/bspeakmon/jira-python/issue/47/, comment on 2013-10-01 by ssonic
    ji = jcl.issue(ji.key,expand="attachments,comments")

    # create the issue
    bbi = jira_issue_to_bb_issue(opts,jcl,ji)
    issues_db['issues'] += [bbi]

    bb_issue_map[bbi['id']] = bbi
    jk_issue_map[ji.key] = bbi
    issue_links[ji.key]['imported from'] = [jira_link_text(ji.key)]

    # add comments
    for jc in ji.fields.comment.comments:
    bbc = jira_comment_to_bb_comment(opts,jcl,jc)
    bbc['issue'] = bbi['id']
    issues_db['comments'] += [bbc]

    # add attachments
    for ja in ji.fields.attachment:
    att_rel_path = os.path.join(opts.att_rel_dir,ja.id)
    att_abs_path = os.path.join(opts.att_abs_dir,ja.id)

    if not os.path.exists(att_abs_path):
    open(att_abs_path,'w').write(ja.get())
    logger.info("Wrote {att_abs_path}".format(att_abs_path=att_abs_path))
    bba = {
    "path": att_rel_path,
    "issue": bbi['id'],
    "user": person_map[ja.author.name],
    "filename": ja.filename,
    }
    issues_db['attachments'] += [bba]

    # parent-child is task-subtask
    if hasattr(ji.fields,'parent'):
    issue_links[ji.fields.parent.key]['subtasks'].append(jira_key_to_bb_issue_tag(ji.key))
    issue_links[ji.key]['parent task'].append(jira_key_to_bb_issue_tag(ji.fields.parent.key))

    # add links
    for il in ji.fields.issuelinks:
    if hasattr(il,'outwardIssue'):
    issue_links[ji.key][il.type.outward].append(jira_key_to_bb_issue_tag(il.outwardIssue.key))
    elif hasattr(il,'inwardIssue'):
    issue_links[ji.key][il.type.inward].append(jira_key_to_bb_issue_tag(il.inwardIssue.key))


    logger.info("migrated issue {ji.key}: {ji.fields.summary} ({components})".format(
    ji=ji,components=','.join(c.name for c in ji.fields.components)))


    # append links section to content
    # this section shows both task-subtask and "issue link" relationships
    for src,dstlinks in issue_links.iteritems():
    if src not in jk_issue_map:
    logger.warn("issue {src}, with issue_links, not in jk_issue_map; skipping".format(src=src))
    continue

    links_block = "Links\n=====\n"
    for desc,dsts in sorted(dstlinks.iteritems()):
    links_block += "* **{desc}**: {links} \n".format(desc=desc,links=", ".join(dsts))

    if jk_issue_map[src]['content']:
    jk_issue_map[src]['content'] += "\n\n" + links_block
    else:
    jk_issue_map[src]['content'] = links_block


    id_counts = collections.Counter(i['id'] for i in issues_db['issues'])
    dupes = [ k for k,cnt in id_counts.iteritems() if cnt>1 ]
    if dupes:
    raise RuntimeError("{n} issue ids appear more than once from existing {opts.json_fn}".format(
    n=len(dupes),opts=opts))

    json.dump(issues_db,open(opts.json_fn,'w'))
    logger.info("wrote {n} issues to {opts.json_fn}".format(n=len(id_counts),opts=opts))


    # write zipfile
    os.chdir(opts.dir)
    with zipfile.ZipFile(opts.dir + '.zip','w') as zf:
    for fn in ['db-1.0.json']+glob.glob('attachments/*'):
    zf.write(fn)
    logger.info("added {fn} to archive".format(fn=fn))

    关于import - 如何将 Jira 问题导出到 BitBucket,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25845589/

    29 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com