- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我刚刚将我的项目代码从 java.net 移到了 BitBucket。但是我的 jira 问题跟踪仍然托管在 java.net 上,尽管 BitBucket 确实有一些选项可以链接到外部问题跟踪器,但我认为我不能将它用于 java.net,尤其是因为我没有管理员权限需要安装 DVCS 连接器。
所以我认为另一种选择是导出然后将问题导入 BitBucket 问题跟踪器,这可能吗?
到目前为止的进展
所以我尝试按照下面使用 OSX 的两个信息丰富的答案中的步骤进行操作,但我遇到了一个问题 - 我对脚本实际调用的内容感到困惑,因为在答案中它谈到了 export.py 但不存在具有该名称的脚本所以我重命名了我下载的那个。
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
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_map
、 person_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/
我在 Bitbucket 中有一个 CI 管道,它正在构建、测试和部署应用程序。 问题是在部署之后我想运行 selenium 测试。 Selenium 测试位于 Bitbucket 的另一个存储库中,
bitbucket 中某些文件中的编辑按钮被禁用。 例如: 但在这个 repo 中,我可以编辑其他文件。 像这样: 我无法理解原因。 最佳答案 请检查文件编码。 我以前也遇到过同样的问题。检查编辑按钮
我已经按照说明创建了一个应用密码 here 但是现在,我如何使用此应用程序密码访问存储库?网址是什么?有人可以将我定向到显示示例的页面吗? 下面是github的代码。我如何为 bitbucket 做到
我最近开始为我的 BitBucket 存储库使用问题跟踪器。我已经能够将所有 Unresolved 问题(只是错误和增强功能)移过去,但我注意到没有像“问题”这样的问题类型。这是我可以添加的东西吗?
我是 bitbuckt 管道的新手。在我的节点项目中,我在管道中添加了 bitbucket-pipelines.yml 我有一个构建容器并将其推送到 ECR 的步骤以及另一个部署步骤。 现在,每次我对
我有一个 Bitbucket 存储库,我可以使用我的凭据登录 Web 浏览器,现在我想在 Sourcetree 中 check out 它,所以我安装了 Sourcetree 2.6.10,它需要使用
我看到与 Bitbucket 管道的矛盾,我认为这是由于我的误解造成的 我想创建一个用于暂存的管道和一个用于生产的管道。这两个管道之间的区别在于每个管道都为 deployment 设置了不同的值,因此
如何找到从未登录过其 Bitbucket 帐户的用户列表?是否有任何查询或宏,还是我必须手动执行? 最佳答案 您可以使用对数据库的 SQL 查询来执行此操作: SELECT cu.lower_
我正在尝试使用代码片段而不是纯文本从相关的松弛 channel 回复关于 bitbucket 的 PR 上的评论。 最佳答案 这很简单。将代码行放在这些 (```) 之间。由于 StackOverfl
如何在 BitBucket 中搜索提交消息?我只能搜索代码,但这对我没有帮助。我要找关键字 HDMI在 提交标题 例如。 最佳答案 Bitbucket 网页上的提交下有一个搜索框。 关于bitbuck
当我尝试设置 slack 集成时出现 403 错误和一个挂起的页面,知道是什么原因造成的吗? 这些是网址 得到bitbucket.org/api/2.0/repositories/MyAccount/
如何更改我的 BitBucket 存储库的开发分支?现在我的 master分支被标记为 main和 development分支。 这是这个问题的几乎重复:How to change the main
是否可以使用他们的 REST API v1.0 通过日期时间过滤器获得对 Bitbucket 的提交?我阅读了 Bitbucket API 的整个文档,但找不到任何相关内容。我在问也许我确实错过了什么
在我的 BitBucket wiki 存储库我已经上传了一张图片,现在我正在尝试将其居中: ![Alt text](https://upload.wikimedia.org/wikipedia/com
我为我的 bitbucket 帐户启用了两因素身份验证。 现在 Osx 上的 Atlassian Sourcetree 应用程序不起作用,无法在 bitbucket 上登录。 我如何配置 Source
我在 bitbucket 管道中有两个存储库,都启用了管道。 另一个管道完成后如何执行管道? 最佳答案 使用 "Bitbucket trigger pipeline" pipe在管道的最后一步。 您可
我们的团队在 Bitbucket 上有一个帐户,我们必须为每个 repo 配置 webhook。有没有办法一次性为所有 repo 配置 webhook,而不是单独管理每个 repo 中的 webhoo
我正在 BitBucket Pipeline 中进行非常简单的 java 构建。唯一的区别是它位于存储库子目录中。 我的 bitbucket-pipelines.yml: pipelines: d
我有一个用于辅助项目的私有(private) bitbucket 存储库。我邀请某人观看它作为采访的一部分。 我决定不再继续采访了。如何删除此观察者? 最佳答案 好的,找到了。 要从存储库中删除观察者
我无法弄清楚或找到有关如何以新的位桶格式访问旧提交源的文档。这还有可能吗? 最佳答案 我了解您希望通过 BitBucket Web 界面下载旧版本,而不使用 Mercurial/Git 客户端。 检查
我是一名优秀的程序员,十分优秀!