Git 常用命令
Update local clone after remote branch name changes
git branch -m <OLD-BRANCH> <NEW-BRANCH>
git fetch origin
git branch -u origin/<NEW-BRANCH> <NEW-BRANCH>
git remote set-head origin -aOne-line example (change master to main):
git branch -m master main && git fetch origin && git branch -u origin/main main && git remote set-head origin -aSee: Renaming a branch - GitHub Docs
Separate subfolder in current Git repo to a new repo
- Create a new folder where would place the new repo
- git clone the original repo under this folder
- Download
git-filter-repofrom here: (Do not chagne its extension)- https://raw.githubusercontent.com/newren/git-filter-repo/main/git-filter-repo
- Place
git-filter-repoto the cloned repo root folder
- cd the cloned repo root folder, and run following:
python git-filter-repo --path <directory_to_separate_to_new_repo> --force - Copy follwing files to root path of the new folder created in step 1:
- Remaining files under
<directory_to_separate_to_new_repo> .gitdirectory in original repo root folder- Run
git statusin the new folder root, to check the git status - Run
git rev-list --count mainto check the commits count of separated directory
- Remaining files under
git add ., and commit the change as a re-organizing of file structure- Create new repo in Github, and get its url, then run:sh
git remote add origin https://github.com/<owner>/<new_repo>.git git remote -v git push -u origin main
See: 将子文件夹拆分成新仓库 - GitHub 文档
See: Quickly rewrite git repository history (filter-branch replacement)
清除 GitHub 上的僵尸提醒
Stuck with a notification for a deleted repository #174843
Bug: ghost notifications #6874
这种一般是因为提醒的仓库被删除了。
清除未读提醒:
gh api notifications\?all=true | jq -r 'map(select(.unread) | .id)[]' | xargs -L1 sh -c 'gh api -X PATCH notifications/threads/$0'如果还想删除侧边栏里这些提醒的仓库列表:
gh api 'notifications?all=true' | jq -r '.[].id' | xargs -I {} gh api -X DELETE 'notifications/threads/{}'make changes in fork repo and make the commits clean
清理 commit 并 rebase
配置 upstream 并拉取最新代码:
git remote add upstream https://github.com/<OLD_OWNER>/<OLD_REPO>.git
git fetch upstream切换到 main 分支并基于 upstream/main 进行 rebase:
git checkout main
git rebase -i upstream/main编辑 commit 记录
在编辑器中,删除不想要的 commit:
pick aaaaaaa change message 1
pick bbbbbbb change message 2
pick ccccccc Merge branch '<OLD_OWNER>:main' into main
pick ddddddd change message 2- 比如这里的
ccccccc是合并 upstream/main 的 commit,可以删除这一行。
同时如果想合成一个 commit,可以把多行 pick 改成 squash 或 s:
pick aaaaaaa change message 1
s bbbbbbb change message 2
s ddddddd change message 2:wq 保存退出。
如果用了 squash,会进入下一个编辑界面,编辑合并后的 commit message:
# This is a combination of 3 commits.
# The first commit's message is:
change message 1
# The 2nd commit's message is:
change message 2
# The 3rd commit's message is:
change message 2编辑成想要的 commit message,保存退出即可。
rebase 出现问题
如果 git rebase -i 的过程中出现了问题,可以回到 rebase 的当前状态:
git rebase --edit-todo或者干脆放弃 rebase:
git rebase --abort强制推送到 fork repo
强制推送到自己 fork 的 repo:
git push --force-with-lease origin main找回误删的 commit
如果多删了 commit,可以通过 git reflog 找回:
git reflog找到对应的 commit id,然后执行:
git checkout main
git cherry-pick <COMMIT_ID>回到 rebase 前的状态
git reflog找到 rebase 前的 commit id,然后执行:
git checkout main
git reset --hard <COMMIT_ID>将 main 分支的改动放到 dev 分支
从 main 分支创建 dev 分支:
git checkout main
git checkout -b dev将 dev 推送到远程:
git push -u origin dev将 main 复位到 upstream/main:
git checkout main
git fetch upstream
git reset --hard upstream/main
git push --force-with-lease origin main使用 main 同步上游,切换 dev 开发
同步上游 main 分支的改动到本地 main 分支,并推送到自己的 fork repo:
git checkout main
git fetch upstream
git merge --ff-only upstream/main
git push origin main在 dev 分支上进行开发:
git checkout -b dev # 或 feature/xxx完成开发和改动后,推送到远程 dev 分支:
git push -u origin devsync with upstream, in both local and remote of forked repo
# git remote -v
# git remote add upstream https://github.com/<OLD_OWNER>/<OLD_REPO>.git
git fetch upstream
git pull upstream main
git push origin main或者一行:
git fetch upstream && git pull upstream main && git push origin main美化 git diff 输出
写入~/.gdc.sh:
gdc() {
emulate -L zsh
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
echo "Not a git repository"
return 1
}
local mode="head"
local revspec=""
local show_untracked=1
local name_only=0
local sort_key=""
local top_n=""
while [[ $# -gt 0 ]]; do
case "$1" in
-c|--cached|-s|--staged)
mode="cached"
show_untracked=0
shift
;;
-w|--worktree)
mode="worktree"
show_untracked=1
shift
;;
-n|--name-only)
name_only=1
shift
;;
-S|--sort)
shift
[[ $# -gt 0 ]] || {
echo "gdc: missing value for --sort"
return 1
}
case "$1" in
add|del|file)
sort_key="$1"
;;
*)
echo "gdc: invalid sort key: $1 (expected: add|del|file)"
return 1
;;
esac
shift
;;
-t|--top)
shift
[[ $# -gt 0 ]] || {
echo "gdc: missing value for --top"
return 1
}
[[ "$1" == <-> ]] || {
echo "gdc: --top expects a positive integer"
return 1
}
top_n="$1"
shift
;;
-h|--help)
cat <<'EOH'
Usage:
gdc [options] [<rev-or-range>]
Modes:
-c, --cached Show staged changes only
-s, --staged Same as --cached
-w, --worktree Show unstaged changes + untracked files
(default) Show diff vs HEAD (staged + unstaged) + untracked files
Display options:
-n, --name-only Show only file names
-S, --sort KEY Sort by: add | del | file
-t, --top N Show only the first N rows after sorting/current order
-h, --help Show this help
Examples:
gdc
gdc -c
gdc -w
gdc HEAD~1
gdc HEAD~3..HEAD
gdc -S add
gdc -S del -t 20
gdc -n -S file
EOH
return 0
;;
--)
shift
break
;;
-*)
echo "gdc: unknown option: $1"
return 1
;;
*)
if [[ -z "$revspec" ]]; then
revspec="$1"
mode="revspec"
show_untracked=1
shift
else
echo "gdc: unexpected extra argument: $1"
return 1
fi
;;
esac
done
local tmp_raw tmp_view
tmp_raw="$(mktemp)" || return 1
tmp_view="$(mktemp)" || {
rm -f "$tmp_raw"
return 1
}
{
case "$mode" in
head)
git diff --numstat HEAD
;;
cached)
git diff --cached --numstat
;;
worktree)
git diff --numstat
;;
revspec)
git diff --numstat "$revspec"
;;
esac
if [[ "$show_untracked" -eq 1 ]]; then
git ls-files --others --exclude-standard -z |
while IFS= read -r -d '' file; do
[[ -f "$file" ]] || continue
printf '%s\t0\t%s\n' "$(wc -l < "$file" 2>/dev/null | tr -d '[:space:]')" "$file"
done
fi
} > "$tmp_raw"
if [[ -n "$sort_key" ]]; then
case "$sort_key" in
add)
LC_ALL=C sort -t $'\t' -k1,1nr -k2,2nr -k3,3 "$tmp_raw" > "$tmp_view"
;;
del)
LC_ALL=C sort -t $'\t' -k2,2nr -k1,1nr -k3,3 "$tmp_raw" > "$tmp_view"
;;
file)
LC_ALL=C sort -t $'\t' -k3,3 "$tmp_raw" > "$tmp_view"
;;
esac
else
cp "$tmp_raw" "$tmp_view"
fi
if [[ -n "$top_n" ]]; then
head -n "$top_n" "$tmp_view" > "${tmp_view}.top"
mv "${tmp_view}.top" "$tmp_view"
fi
if [[ "$name_only" -eq 1 ]]; then
awk -F '\t' '
BEGIN {
blue = "\033[34m"
reset = "\033[0m"
}
NF >= 3 && $3 != "" {
file = $3
dir = ""
base = file
if (match(file, /.*\//)) {
dir = substr(file, 1, RLENGTH)
base = substr(file, RLENGTH + 1)
}
printf "%s" blue "%s" reset "\n", dir, base
}
' "$tmp_view"
else
awk -F '\t' '
BEGIN {
green = "\033[32m"
red = "\033[31m"
blue = "\033[34m"
magenta = "\033[35m"
bold = "\033[1m"
reset = "\033[0m"
add_sum = 0
del_sum = 0
n = 0
maxw = 3
filew = 4
}
NF < 3 || $3 == "" { next }
{
adds[++n] = $1
dels[n] = $2
files[n] = $3
if ($1 ~ /^[0-9]+$/) {
add_sum += $1
if (length($1) > maxw) maxw = length($1)
}
if ($2 ~ /^[0-9]+$/) {
del_sum += $2
if (length($2) > maxw) maxw = length($2)
}
if (length($3) > filew) filew = length($3)
}
END {
if (length(add_sum "") > maxw) maxw = length(add_sum "")
if (length(del_sum "") > maxw) maxw = length(del_sum "")
sep_num = sprintf("%" maxw "s", "")
sep_file = sprintf("%" filew "s", "")
gsub(/ /, "-", sep_num)
gsub(/ /, "-", sep_file)
printf magenta bold "%" maxw "s %" maxw "s %-" filew "s\n" reset, "ADD", "DEL", "FILE"
printf magenta "%s %s %s\n" reset, sep_num, sep_num, sep_file
for (i = 1; i <= n; i++) {
file = files[i]
dir = ""
base = file
if (match(file, /.*\//)) {
dir = substr(file, 1, RLENGTH)
base = substr(file, RLENGTH + 1)
}
printf green "%" maxw "s" reset " " red "%" maxw "s" reset " %s" blue "%s" reset "\n",
adds[i], dels[i], dir, base
}
if (n > 1) {
printf magenta "%s %s %s\n" reset, sep_num, sep_num, sep_file
printf green "%" maxw "d" reset " " red "%" maxw "d" reset " %s\n", add_sum, del_sum, "TOTAL"
}
}
' "$tmp_view"
fi
local rc=$?
rm -f "$tmp_raw" "$tmp_view" "${tmp_view}.top" 2>/dev/null
return $rc
}在 ~/.zshrc 中添加:
[[ -f ~/.gdc.sh ]] && source ~/.gdc.sh或者实时更新:
source ~/.zshrc用法:
gdc -hUsage:
gdc [options] [<rev-or-range>]
Modes:
-c, --cached Show staged changes only
-s, --staged Same as --cached
-w, --worktree Show unstaged changes + untracked files
(default) Show diff vs HEAD (staged + unstaged) + untracked files
Display options:
-n, --name-only Show only file names
-S, --sort KEY Sort by: add | del | file
-t, --top N Show only the first N rows after sorting/current order
-h, --help Show this help
Examples:
gdc
gdc -c
gdc -w
gdc HEAD~1
gdc HEAD~3..HEAD
gdc -S add
gdc -S del -t 20
gdc -n -S file