git 个人开发工作流
1618 字
8 分钟
git 个人开发工作流
日常工作流
feature-a 某个上游的基线分支
feature-a-xxx 基于 feature-a 的本地开发分支
多人开发时,优先在 feature-a-xxx 上提交自己的代码,定时通过 rebase 同步上游 feature-a 的改动,最后 通过 merge 方式把本地的提交 合并回上游
feature-a----A---------B-------------C-------------------M(merge back)---- \ \ / \ \ /feature-a-xxx ----a1--------a2----rebase to C----a3---本地开发时,可能会存在多次临时的提交,建议通过 squash 将本地的提交整理后再 merge 回上游分支
feature-a----A---------B---------C---------D-------------------------------M---- \ \ \ / \ \ \ /feature-a-xxx ----a1----a2----rebase----a3----rebase----a4----a5----S(squash)以下脚本主要添加了两个方便快速操作的实现
grebase 在本地开发分支上快速 rebase 上游分支
gmergebase 将本地开发分支 merge 回上游分支
# ============================================# Git personal branch helpers - safe edition# --------------------------------------------# 约定:# 个人开发分支 = <基线分支>-<个人后缀># 或 = <基线分支>/<个人后缀>## 示例:# feature-a-sephy -> feature-a# feature-order-refactor-jack -> feature-order-refactor# feature-a/sephy -> feature-a## 提供命令:# gbase # 输出自动识别出的基线分支# gplan # 预览 rebase / merge 计划# grebase # 安全 rebase 到 origin/<base># grebase --autostash # 工作区不干净时自动暂存后 rebase# gmergebase # merge 当前分支回基线分支,merge 后切回当前分支# gmergebase --push # merge 后自动 push# gmergebase --squash # squash merge# gmergebase --push --delete# # merge + push + 删除已合并个人分支(本地)# ============================================
# ---------- 基础输出 ----------
__gpb_info() { printf '[INFO] %s\n' "$*"; }__gpb_warn() { printf '[WARN] %s\n' "$*" >&2; }__gpb_err() { printf '[ERR ] %s\n' "$*" >&2; }
# ---------- 基础判断 ----------
__gpb_in_git_repo() { git rev-parse --is-inside-work-tree >/dev/null 2>&1}
__gpb_current_branch() { git branch --show-current 2>/dev/null}
__gpb_branch_exists_local() { local branch="$1" git show-ref --verify --quiet "refs/heads/$branch"}
__gpb_branch_exists_remote() { local branch="$1" git show-ref --verify --quiet "refs/remotes/origin/$branch"}
__gpb_branch_exists_any() { local branch="$1" __gpb_branch_exists_local "$branch" || __gpb_branch_exists_remote "$branch"}
__gpb_all_branches() { { git for-each-ref --format='%(refname:short)' refs/heads git for-each-ref --format='%(refname:short)' refs/remotes/origin } | sed 's#^origin/##' | grep -v '^HEAD$' | awk '!seen[$0]++'}
# 工作区是否干净(含 staged / unstaged,忽略 untracked)__gpb_worktree_clean() { git diff --quiet && git diff --cached --quiet}
# 是否存在未跟踪文件__gpb_has_untracked() { [ -n "$(git ls-files --others --exclude-standard)" ]}
# 当前分支是否有未推送提交__gpb_has_unpushed_commits() { local current upstream current="$(__gpb_current_branch)" upstream="$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)" || return 1 [ -n "$(git rev-list "${upstream}..HEAD" 2>/dev/null)" ]}
# 当前分支是否落后于上游__gpb_is_behind_upstream() { local upstream upstream="$(git rev-parse --abbrev-ref --symbolic-full-name @{u} 2>/dev/null)" || return 1 [ -n "$(git rev-list "HEAD..${upstream}" 2>/dev/null)" ]}
# 基线分支是否包含当前开发分支(用于删除前校验)__gpb_is_merged_into() { local src="$1" local target="$2" git merge-base --is-ancestor "$src" "$target"}
# ---------- 基线推导 ----------
__gpb_infer_base_branch() { local current="$1" local best="" local candidate=""
[ -z "$current" ] && return 1
while IFS= read -r candidate; do [ -z "$candidate" ] && continue [ "$candidate" = "$current" ] && continue
case "$current" in "$candidate"-*|"$candidate"/*) if [ ${#candidate} -gt ${#best} ]; then best="$candidate" fi ;; esac done < <(__gpb_all_branches)
if [ -n "$best" ]; then printf '%s\n' "$best" return 0 fi
if [[ "$current" == *-* ]]; then local by_dash="${current%-*}" if __gpb_branch_exists_any "$by_dash"; then printf '%s\n' "$by_dash" return 0 fi fi
if [[ "$current" == */* ]]; then local by_slash="${current%/*}" if __gpb_branch_exists_any "$by_slash"; then printf '%s\n' "$by_slash" return 0 fi fi
return 1}
gbase() { __gpb_in_git_repo || { __gpb_err "Not a git repository"; return 1; }
local current base current="$(__gpb_current_branch)" [ -n "$current" ] || { __gpb_err "Cannot determine current branch"; return 1; }
base="$(__gpb_infer_base_branch "$current")" || { __gpb_err "Cannot infer base branch from current branch: $current" return 1 }
printf '%s\n' "$base"}
# ---------- 计划预览 ----------
gplan() { __gpb_in_git_repo || { __gpb_err "Not a git repository"; return 1; }
local current base current="$(__gpb_current_branch)" [ -n "$current" ] || { __gpb_err "Cannot determine current branch"; return 1; }
base="$(__gpb_infer_base_branch "$current")" || { __gpb_err "Cannot infer base branch from current branch: $current" return 1 }
cat <<EOFCurrent branch : $currentBase branch : $base
Rebase: git fetch origin --prune git rebase origin/$base
Merge back: git fetch origin --prune git checkout $base git pull --ff-only origin $base git merge --no-ff $current git checkout $currentEOF}
# ---------- 安全 rebase ----------
grebase() { __gpb_in_git_repo || { __gpb_err "Not a git repository"; return 1; }
local autostash=0 while [ $# -gt 0 ]; do case "$1" in --autostash) autostash=1 ;; *) __gpb_err "Unknown option: $1" return 1 ;; esac shift done
local current base stashed=0 stash_msg current="$(__gpb_current_branch)" [ -n "$current" ] || { __gpb_err "Cannot determine current branch"; return 1; }
base="$(__gpb_infer_base_branch "$current")" || { __gpb_err "Cannot infer base branch from current branch: $current" return 1 }
if [ "$current" = "$base" ]; then __gpb_err "Current branch is already the base branch: $base" return 1 fi
__gpb_info "Current branch : $current" __gpb_info "Base branch : $base"
if ! __gpb_worktree_clean; then if [ "$autostash" -eq 1 ]; then stash_msg="grebase-autostash-$(date +%Y%m%d%H%M%S)" __gpb_warn "Working tree is dirty, auto stashing changes: $stash_msg" git stash push -u -m "$stash_msg" >/dev/null || { __gpb_err "git stash failed" return 1 } stashed=1 else __gpb_err "Working tree is dirty. Commit/stash first, or use: grebase --autostash" return 1 fi fi
git fetch origin --prune || { [ "$stashed" -eq 1 ] && __gpb_warn "Your changes are stashed. Restore manually if needed: git stash list" return 1 }
if ! __gpb_branch_exists_remote "$base"; then __gpb_err "Remote branch origin/$base does not exist" [ "$stashed" -eq 1 ] && __gpb_warn "Your changes are stashed. Restore manually if needed: git stash pop" return 1 fi
if ! git rebase "origin/$base"; then __gpb_err "Rebase failed. Resolve conflicts, then run:" printf ' git add <files>\n' printf ' git rebase --continue\n' printf ' # or git rebase --abort\n' [ "$stashed" -eq 1 ] && __gpb_warn "Auto-stash was created. Apply it after rebase is fully done." return 1 fi
if [ "$stashed" -eq 1 ]; then __gpb_info "Restoring stashed changes" if ! git stash pop; then __gpb_warn "git stash pop had conflicts or was not fully applied. Check: git stash list" return 1 fi fi
__gpb_info "Rebase completed successfully"}
# ---------- 安全 merge back ----------
gmergebase() { __gpb_in_git_repo || { __gpb_err "Not a git repository"; return 1; }
local do_push=0 local do_squash=0 local do_delete=0
while [ $# -gt 0 ]; do case "$1" in --push) do_push=1 ;; --squash) do_squash=1 ;; --delete) do_delete=1 ;; *) __gpb_err "Unknown option: $1" return 1 ;; esac shift done
local dev base original_branch original_branch="$(__gpb_current_branch)" dev="$original_branch"
[ -n "$dev" ] || { __gpb_err "Cannot determine current branch"; return 1; }
base="$(__gpb_infer_base_branch "$dev")" || { __gpb_err "Cannot infer base branch from current branch: $dev" return 1 }
if [ "$dev" = "$base" ]; then __gpb_err "Current branch is already the base branch: $base" return 1 fi
__gpb_info "Dev branch : $dev" __gpb_info "Base branch : $base"
if ! __gpb_worktree_clean; then __gpb_err "Working tree is dirty. Please commit/stash first." return 1 fi
if __gpb_has_untracked; then __gpb_warn "There are untracked files in working tree" fi
git fetch origin --prune || return 1
if __gpb_has_unpushed_commits; then __gpb_warn "Current branch has unpushed commits" __gpb_warn "Consider pushing first for backup" fi
if __gpb_is_behind_upstream; then __gpb_warn "Current branch is behind its upstream" fi
if ! __gpb_branch_exists_any "$base"; then __gpb_err "Base branch does not exist locally/remotely: $base" return 1 fi
git checkout "$base" || return 1
if __gpb_branch_exists_remote "$base"; then git pull --ff-only origin "$base" || { __gpb_err "Failed to fast-forward local $base from origin/$base" git checkout "$original_branch" >/dev/null 2>&1 return 1 } fi
if [ "$do_squash" -eq 1 ]; then __gpb_info "Running squash merge" git merge --squash "$dev" || { __gpb_err "Squash merge failed" git checkout "$original_branch" >/dev/null 2>&1 return 1 }
__gpb_warn "Squash merge staged changes only; commit manually:" printf ' git commit -m "merge %s into %s"\n' "$dev" "$base"
git checkout "$original_branch" >/dev/null 2>&1 || true return 0 fi
__gpb_info "Running merge --no-ff" git merge --no-ff "$dev" || { __gpb_err "Merge failed. Resolve conflicts, then commit." __gpb_warn "After merge is completed manually, you may switch back with: git checkout $original_branch" return 1 }
if [ "$do_push" -eq 1 ]; then __gpb_info "Pushing $base to origin" git push origin "$base" || { __gpb_err "Push failed" git checkout "$original_branch" >/dev/null 2>&1 || true return 1 } fi
if [ "$do_delete" -eq 1 ]; then if __gpb_is_merged_into "$dev" "$base"; then __gpb_info "Deleting merged local branch: $dev" git branch -d "$dev" || { __gpb_warn "Failed to delete local branch: $dev" } else __gpb_warn "Skip deleting $dev because it is not fully merged into $base" fi fi
__gpb_info "Switching back to original branch: $original_branch" git checkout "$original_branch" >/dev/null 2>&1 || { __gpb_warn "Failed to switch back automatically. Please run: git checkout $original_branch" return 0 }
__gpb_info "Merge back completed successfully"}
# ---------- 可选快捷别名 ----------alias grb='grebase'alias gmb='gmergebase'文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
相关文章智能推荐
1
mihomo 使用
技术分享2025-09-30
2
通过 chezmoi 来管理 dotfiles
技术分享2024-05-06
3
在 PVE 上给软路由虚拟机加一个网络 Watchdog
技术分享在 PVE 宿主机上用 Bash 和 systemd timer 实现一个网络 watchdog:先检测公网连通性,连续失败后重启软路由虚拟机,多次自愈无效再升级为重启 PVE 宿主机。
4
命令行自动登录 JumpServer:SSH、TOTP 与密码提示的自动化
技术分享把 JumpServer 登录流程拆成凭据读取和终端交互两层:zsh 从密码管理器安全读取 TOTP seed 与可选密码,expect 只负责响应 SSH 登录过程中的 password 和 Code 提示。
5
使用 docker-atrust 连入深信服
技术分享使用 Docker 容器运行深信服 aTrust 客户端,通过 VNC 完成登录,并结合本机 mihomo 配置实现公司内网的 DNS 解析与按域名、IP 的精确代理分流;宿主机暴露 1080 与 8888 端口供 socks5 和 HTTP 代理使用,从而在不安装原生客户端的情况下实现零信任接入并降低对本机的影响。
随机文章随机推荐













