def merge(self): """ 将生产分支最新代码merge到当前分支 步骤: 1. 拉取生产分支: git fetch origin master && git checkout master && git rebase origin/master 2. 拉取当前分支: git fetch origin currbranch && git checkout currbranch && git rebase origin/currbranch 3. 将master合并到当前分支: git checkout currbranch && git merge master :return: """ prod_branch = igit.product_branch() curr_branch = igit.current_branch() if curr_branch == prod_branch: return #进入生产分支 ihelper.execute('git checkout %s' % prod_branch) #拉取生产远程分支到本地 igit.pull() #切回开发分支 ihelper.execute('git checkout %s' % curr_branch) #拉取远程开发分支到本地 igit.pull() #合并本地master到本地开发分支并推送到远程 igit.merge(prod_branch, need_pull=False) igit.set_last_sync_master_date(curr_branch) ok('done!')
def tag(self): """ 在生产分支上打标签 :return: """ if not self.args: return ihelper.execute('git tag') #将要在该分支上打标签 tag_branch = igit.product_branch() # 当前工作空间是否干净 if igit.current_branch() != tag_branch and not igit.workspace_is_clean( ): raise exception.FlowException(u'工作区中尚有未保存的内容') tag_name = None comment = '' while self.args: c = self.args.pop(0) if c == '-a': tag_name = self.args.pop(0) elif c == '-m': while self.args: if self.args[0].startswith('-'): break comment += self.args.pop(0) + ' ' if not comment: raise exception.FlowException(u'请输入标签注释') if not tag_name: tag_name = igit.tag_name() if not tag_name: raise exception.FlowException(u'未设置tag name') if ihelper.confirm(u'将在分支 %s 上打tag:%s, ok?' % (tag_branch, tag_name)) != 'y': warn(u'取消操作') return c_branch = igit.current_branch() try: #切换分支 if c_branch != tag_branch: info(u'切换到分支 %s:' % tag_branch) ihelper.execute('git checkout %s' % tag_branch) #打tag print igit.tag(tag_name, comment) except exception.FlowException, e: error(u'操作失败:') raise e
def __tag_list(pattern): """ 根据pattern获取git tag列表 :param pattern: :return: """ if pattern: return ihelper.execute('git tag -l "%s" --sort "version:refname"' % pattern, print_out=False, return_result=True).splitlines() else: return ihelper.execute('git tag --sort "version:refname"', print_out=False, return_result=True).splitlines()
def clear(args=None): """ 清屏 :param args: :return: """ if ihelper.system_type() == iglobal.PLATFORM_WINDOWS: ihelper.execute('cls') else: ihelper.execute('clear')
def exec_hook(flag, position, proj=None, branch=None): """ 执行钩子指令 :param flag: 标识,如product :param position: 位置,如post(主指令后),pre(主指令前) :param proj: 所在的项目 :param branch: 所在的分支 :return: None """ hook_cfg = iconfig.read_config('system', "hook") if not hook_cfg or not hook_cfg.has_key(flag) or not isinstance(hook_cfg[flag], dict): return None hook_cfg = hook_cfg[flag] if not hook_cfg.has_key(position) or not isinstance(hook_cfg[position], list) or not hook_cfg[position]: return None orig_dir = os.getcwd() orig_branch = igit.current_branch() for cfg in hook_cfg[position]: if not cfg or not isinstance(cfg, dict) or not cfg.has_key('cmd'): continue # 项目限制 if proj and cfg.has_key("proj") \ and isinstance(cfg["proj"], list) \ and cfg["proj"] \ and proj not in cfg["proj"]: continue # 分支限制 if branch and cfg.has_key("branch") \ and isinstance(cfg["branch"], list) \ and cfg["branch"] \ and branch not in cfg["branch"]: continue if not cfg.has_key("title") or not cfg["title"]: cfg["title"] = u"自定义脚本:%s" % cfg["cmd"] # 执行钩子 iprint.say(('green', u'执行'), ('yellow', cfg["title"]), ('green', '...')) ihelper.execute(cfg["cmd"]) iprint.say(('yellow', cfg["title"]), ('green', u'执行完成!')) if os.getcwd() != orig_dir: os.chdir(orig_dir) if igit.current_branch() != orig_branch: os.system('git checkout %s' % orig_branch)
def pull(): """ 拉取当前分支 :return: """ fetch(branch=current_branch()) if not workspace_at_status(iglobal.GIT_BEHIND) and not workspace_at_status(iglobal.GIT_DIVERGED): return ihelper.execute('git rebase') if workspace_at_status(iglobal.GIT_CONFLICT): raise exception.FlowException(u'拉取远程分支出错:冲突。请手工解决冲突后执行git add . && git rebase --continue,然后再重新执行命令')
def tag(tag_name, comment): """ 打标签 :return: """ out = ihelper.execute('git tag -a %s -m "%s"' % (tag_name, comment), print_out=False) if (out.find('already exists') > -1): raise exception.FlowException(u'已存在该标签') out = ihelper.execute('git push origin %s' % tag_name, print_out=False) if (out.find('already exists') > -1): raise exception.FlowException(u'远程已存在该标签') return out
def sync(project, prefix=None, only_this_sprint=True, deep=False): """ 同步本地和远程分支 :param bool deep: 是否深层合并,只有深层合并才会同步本地和远程都存在的分支(暂未实现),否则只是创建本地没有的分支和推远程没有的分支 :param project: :param prefix: :param only_this_sprint: :return: """ iglobal.SILENCE = True old_project = iglobal.PROJECT try: # 进入该项目目录 if project != iglobal.PROJECT: command.Extra('cd', [project]).execute() if prefix and only_this_sprint: prefix = '%s/%s' % (prefix.rstrip('/'), iglobal.SPRINT) # remote_branches里面已经执行了git fetch了,此处就不再执行了 l_brs = local_branches() r_brs = remote_branches() if prefix: l_brs = filter(lambda x:str(x).startswith(prefix), l_brs) r_brs = filter(lambda x:str(x).startswith(prefix), r_brs) local_only = list(set(l_brs).difference(set(r_brs))) remote_only = list(set(r_brs).difference(set(l_brs))) the_same = list(set(l_brs).intersection(set(r_brs))) # 仅在本地存在的,推到远程去 for l_o_br in local_only: ihelper.execute('git push -u origin %s:%s' % (l_o_br, l_o_br)) # 仅远程存在的,创建本地相关分支 for r_o_br in remote_only: ihelper.execute('git branch %s origin/%s' % (r_o_br, r_o_br)) # 两边都存在的,同步 if the_same and deep: raise exception.FlowException(u'暂未实现') if old_project != iglobal.PROJECT: command.Extra('cd', [old_project]).execute() finally: iglobal.SILENCE = False
def delete_branch(branchName, del_remote=False): """ 删除分支 :param branchName: :param del_remote: :return: """ if branchName == current_branch(): raise exception.FlowException('can not delete current branch') info(u'删除本地分支 %s ...' % branchName) ihelper.execute('git branch -D %s' % branchName, raise_err=True) #删除远程分支 if del_remote: info(u'删除远程分支 origin/%s ...' % branchName) ihelper.execute('git push --delete origin %s' % branchName) ok(u'删除成功!')
def push(branch=None, need_pull=True): """ 将当前分支推到远程仓库 :param branch: :param need_pull: push之前是否pull :return: """ if not branch: branch = current_branch() if not branch or (not workspace_at_status(iglobal.GIT_AHEAD) and not workspace_at_status(iglobal.GIT_DIVERGED)): return # 先拉远程分支(如果报错则需要手工处理) if need_pull: pull() ihelper.execute('git push origin ' + branch + ':' + branch)
def remote_branches(refresh=True): """ 远程分支列表 :param refresh: """ # 先拉一下最新的远程分支 if refresh: fetch() return map(lambda x:str(x).replace('origin/HEAD -> ', '').replace('origin/', '').strip(), ihelper.execute('git branch -r', print_out=False, return_result=True).splitlines())
def rename(self): """ 分支重命名。old和new需要时绝对分支名 """ if len(self.args) < 2: raise exception.FlowException('指令格式错误,请输入help查看使用说明') old = self.args[0] new = self.args[1] local_branches = igit.local_branches() if old not in local_branches: raise exception.FlowException(u'分支名称不存在:%s' % old) remote_branches = igit.remote_branches() # 名称重复性检测 if new in local_branches or new in remote_branches: raise exception.FlowException(u'该分支已经存在:%s' % new) info(u'重命名分支:%s -> %s...' % (old, new)) # 重命名本地分支 ihelper.execute('git branch -m ' + old + ' ' + new, raise_err=True) # 删除远程分支(如果有的话) if old in remote_branches: ihelper.execute('git push --delete origin ' + old) # 上传新分支到远程 ihelper.execute('git push -u origin ' + new + ':' + new) #防止重命名后检查master更新情况 igit.set_last_sync_master_date(new)
def commit(self): """ 提交 :return: """ comment = '' push = False if not self.args: raise exception.FlowException(u'指令格式错误,请输入h ft查看使用说明') while self.args: c = self.args.pop(0) if c == '-p': push = True else: comment += ' %s' % c comment = comment.strip() if not comment: raise exception.FlowException(u'请填写提交说明') curr_branch = igit.current_branch() # 提交 ihelper.execute('git add .') ihelper.execute('git commit -m "' + igit.comment(comment, curr_branch.split('/')[0]). decode('utf-8').encode(iglobal.FROM_ENCODING) + '"') if push: igit.push(curr_branch) ok(u'提交' + (u'并推送到远程仓库' if push else '') + u'成功!')
def checkout(self, args): """ 切换到某个特性/修复分支并拉取最新代码(如果工作空间是干净的) :return: """ branch = None sync_remote = False while args: c = args.pop(0) if c == '-r' or c == '--remote': sync_remote = True else: branch = igit.real_branch(c, self.cmd) current_branch = igit.current_branch() if not branch: branch = current_branch if branch == current_branch: sync_remote = True __error = False if branch != current_branch: # 检查当前分支下工作区状态 if not igit.workspace_is_clean(): raise exception.FlowException( u'工作区中尚有未提交的内容,请先用commit提交或用git stash保存到Git栈中') out = ihelper.execute('git checkout ' + branch, return_result=True) # git的checkout指令输出在stderr中 if 'Switched to branch' not in out: __error = True if __error: warn(u'checkout失败') return if sync_remote: info('fetch from remote...') igit.fetch(branch=branch) if igit.workspace_at_status( iglobal.GIT_BEHIND) or igit.workspace_at_status( iglobal.GIT_BEHIND): warn(u'远程仓库已有更新,请执行 git rebase 获取最新代码')
def test(self, args): """ 发布到测试分支,只允许单个分支发布 :param args: :return: """ # 当前工作空间是否干净 if not igit.workspace_is_clean(): raise exception.FlowException(u'工作区中尚有未保存的内容') if not args: branch = igit.current_branch() else: branch = args.pop(0) branch = igit.real_branch(branch, self.cmd) if branch != igit.current_branch(): # 切换分支 info(u'切换到分支%s' % branch) ihelper.execute('git checkout ' + branch) # 当前工作空间是否干净 if not igit.workspace_is_clean(): raise exception.FlowException(u'工作区中尚有未保存的内容') igit.sync_branch() # 切换到test分支 test_branch = igit.test_branch() info(u'切换到分支%s' % test_branch) ihelper.execute('git checkout ' + test_branch) # 当前工作空间是否干净 if not igit.workspace_is_clean(): raise exception.FlowException(u'工作区中尚有未保存的内容') # 合并 info(u'正在将%s合并到%s上...' % (branch, test_branch)) igit.merge(branch) # 正常执行后,工作空间应该是干净的 if not igit.workspace_is_clean(): raise exception.FlowException(u'合并失败,请用git status查看工作空间详情') # 将test推到远程 igit.push() # 切换到原来的分支 info(u'切换回%s' % branch) ihelper.execute('git checkout ' + branch) ok(u'合并到' + test_branch + u'成功!')
def merge(branch, need_push=True, need_pull=True): """ 将branch合并到当前分支 :param need_pull: :param need_push: :param branch: :return: """ # 从远程仓库拉最新代码 if need_pull: pull() # 合并(git合并冲突信息在stdout中) ihelper.execute('git merge --no-ff ' + branch) if workspace_at_status(iglobal.GIT_CONFLICT): ihelper.execute('git status -s') raise exception.FlowException(u'合并失败:发生冲突。请手工解决冲突后执行git add . && git commit,然后重新执行命令') # 推送到远程仓库 if need_push: ihelper.execute('git push')
def publish_to_master(self, branches=None): """ 发布分支到生产环境 :param branches: 待发布分支列表:[(proj_name, branch)] """ curr_p_branch = None tag_list = [] if not branches: #接着上次的继续发布 branches = ihelper.read_runtime('publish_branches') tag_list = ihelper.read_runtime('publish_tags') or [] if not branches: info(u'没有需要发布的分支') return orig_branches = list(branches) try: curr_proj = None for index, item in enumerate(branches): curr_p_branch = item proj, branch = tuple(item) if iglobal.PROJECT != proj: info(u'进入项目%s' % proj) extra.Extra('cd', [proj]).execute() if not igit.workspace_is_clean(): raise exception.FlowException( u'项目%s工作空间有未提交的更改,请先提交(或丢弃)后执行 %s p --continue 继续' % (proj, self.cmd)) # 首次进入项目执行fetch获取本地和远程分支差异 if curr_proj != proj: info('fetch...') igit.fetch(useCache=False) # 切换到将要合并的分支(如果不存在本地分支则会自动创建) ihelper.execute('git checkout %s' % branch) # 同步当前本地和远程分支(此处可能会出现冲突) igit.sync_branch() # 切换到master分支 ihelper.execute('git checkout %s' % igit.product_branch()) is_last_branch = index >= len( branches) - 1 or proj != branches[index + 1][0] # 合并 info(u'合并%s...' % branch) igit.merge(branch, need_pull=proj != curr_proj, need_push=is_last_branch) info(u'合并完成:%s' % branch) # 完成 orig_branches.remove(curr_p_branch) if proj != curr_proj: curr_proj = proj # 本项目发布完成后的一些操作 if is_last_branch: self.__post_publish(proj, tag_list) ok(u'发布完成!tag:') # 打印tag信息 for (proj_name, tag_name) in tag_list: info(' %s %s' % (proj_name, tag_name)) # 清空tag list tag_list = [] except Exception, e: error(e.message) warn(u'处理完成后执行 %s p --continue 继续。或执行 %s p --abort 结束' % (self.cmd, self.cmd))
def git(self): """ git原生指令 """ ihelper.execute('git ' + (' '.join(self.args) if self.args else ''))
try: #切换分支 if c_branch != tag_branch: info(u'切换到分支 %s:' % tag_branch) ihelper.execute('git checkout %s' % tag_branch) #打tag print igit.tag(tag_name, comment) except exception.FlowException, e: error(u'操作失败:') raise e finally: if c_branch != igit.current_branch(): info(u'切换回 %s' % c_branch) ihelper.execute('git checkout %s' % c_branch) ok(u'操作成功!') def delete(self): """ 删除本地匹配模式的多个分支,并删除远程相应分支 :return: """ if not self.args: raise exception.FlowException(u'指令格式错误,请输入help查看使用说明') tag_pattern = None del_remote = True del_branches = []
def create(self, args): """ 创建特性/修复分支。一次只能创建一个,会推到远端,且切换到此分支 :return: """ if not args: raise exception.FlowException(u'指令格式错误,请输入h %s查看使用说明' % self.cmd) branch = None auto_create_from_remote = False push_to_remote = True while args: c = args.pop(0) if c == '-y': auto_create_from_remote = True elif c == '--np' or c == '--no-push': push_to_remote = False else: branch = c if not branch: raise exception.FlowException(u'请输入分支名称') # 分支简称不可与项目、迭代名称相同(防止一些指令出现歧义) simple_branch = igit.simple_branch(branch) if simple_branch == iglobal.SPRINT or simple_branch in ihelper.projects( ): raise exception.FlowException(u'分支简称不可与项目或迭代名同名') branch = igit.real_branch(branch, self.cmd) # 检查当前分支下工作区状态 if not igit.workspace_is_clean(): raise exception.FlowException( u'工作区中尚有未提交的内容,请先用git commit提交或用git stash保存到Git栈中') # 分支名称重复性检查 info(u'检查本地分支...') if branch in igit.local_branches(): raise exception.FlowException(u'该分支名称已经存在') # 本地没有但远程有 create_from_remote = False info(u'检查远程分支...') if branch in igit.remote_branches(): if not auto_create_from_remote and ihelper.confirm( u'远程仓库已存在%s,是否基于该远程分支创建本地分支?' % branch) != 'y': return else: create_from_remote = True say(('white', u'正在创建分支'), ('sky_blue', branch), ('white', '...')) if create_from_remote: # 基于远程分支创建本地分支,会自动追踪该远程分支 ihelper.execute('git checkout -b ' + branch + ' origin/' + branch) else: # 切换到生产分支 p_branch = igit.product_branch() ihelper.execute('git checkout ' + p_branch) igit.pull() # 基于本地生产分支创建新分支 ihelper.execute('git checkout -b ' + branch) # 推送到远程 if push_to_remote: ihelper.execute('git push -u origin ' + branch + ':' + branch) if igit.workspace_is_clean(): #处理master更新检验,防止创建分支后执行master更新检查操作 igit.set_last_sync_master_date(branch) ok(u'创建成功!已进入分支:' + branch) else: raise exception.FlowException(u'创建分支失败')