本文为读《Pro Git》的笔记,结合自己在实践中遇到的情景与问题,做一个记录。
0 开始
在一台机器上初次使用git的配置:git config
,记录基本2条: [user] (1) name = xxx (2) email = xxx@yyy
有三个范围,system,global和local。
config 层次 | 位置 | 作用范围 |
---|---|---|
system | /etc/gitconfig (通常) | 所有用户的所有仓库的通用配置 |
global | ~/.gitconfig 或 ~/.config/git/config | 当前用户的所有仓库的配置 |
local | .git/config (当前仓库下) | 当前用户的当前仓库的特定配置 |
下层配置覆盖上层配置。 |
- 查看所有配置:
git config --list
(可用-l
) - 查看某一个配置:
git config <key>
,如user.name - 进行配置:
git config --system/global/local <key> <value>
, 如最基础的设置名字和邮箱,--global user.name "xxx"
--global user.email xxx@yyy
,还有设置editor:git config --global core.editor vi
。
1 初始化
两种方式初始化一个git repository(仓库):
git init
git clone <url> (<repo name>)
repo name可选。其实是重命名克隆到本地的仓库。
git clone:克隆的是该 Git 仓库服务器上的几乎所有数据。默认配置下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来。
<url>
可以使用 https:// 协议、git:// 协议或者使用 SSH 传输协议,比如user@server:path/to/repo.git 。
2 文件操作
-
查看状态:
git status
Untracked的文件意味着 Git 在之前的快照(commit)中没有这些文件;Git 不会自动将之纳入跟踪范围,除非明明白白地告诉它“我需要跟踪该文件”。 这样的处理可以避免生成的二进制文件或其它不想被跟踪的文件包含进来。 -
添加文件:
git add <file list or directory>
, 如果是dir,则递归add dir下所有文件。
git add
是个多功能命令:可以用它(1)开始跟踪新文件,(2)把已跟踪的文件放到暂存区,(3)合并时把有冲突的文件标记为已解决状态等。 将这个命令理解为“精确地将内容添加到下一次提交中”。因此上图中,“Add the file” “Stage the file” 均为git add
命令。 -
取消修改:
git checkout -- <file>
或git restore <file>
(v 2.23+)
(modified->unmodified) -
取消暂存:
git reset HEAD <file>
或git restore --staged <file>
(v 2.23+)
(staged->modified) -
忽略文件。靠事先的.gitignore。
有些文件无需纳入 Git 的管理,也不希望它们出现在未跟踪文件列表。 通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。 在这种情况下,可以创建一个名为 .gitignore 的文件,列出要忽略的文件的模式。
文件 .gitignore 的格式规范如下:
所有空行或者以 # 开头的行都会被 Git 忽略。
可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。
匹配模式可以以(/)开头防止递归。
匹配模式可以以(/)结尾指定目录。
要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反。
# 忽略所有的 .a 文件
*.a
# 但跟踪所有的 lib.a,即便在前面忽略了 .a 文件
!lib.a
# 只忽略当前目录下的 TODO 文件,而不忽略 subdir/TODO
/TODO
# 忽略任何目录下名为 build 的文件夹
build/
# 忽略 doc/notes.txt,但不忽略 doc/server/arch.txt
doc/*.txt
# 忽略 doc/ 目录及其所有子目录下的 .pdf 文件
doc/**/*.pdf
-
对比差异:
git diff (--staged/cached)
查看工作区和暂存区之间的差异(–stage/cached暂存区和上次提交的差异) -
提交:
git commit
-
移除:
git rm (-f) (--cached) <file>
如果没有–cached,则会在工作目录中删除未修改的文件,并不纳入git管理(类似ignore);
如果有-f,没有–cached,则会在工作做目录中删除修改过的文件和已暂存文件,并不纳入git管理(类似ignore);
如果有–cached,则不会从工作目录中删除,但会删除暂存区中的文件,并从此ignore。 -
重命名:
git mv <a> <b>
-
历史:
git log
,很常用,很多扩展选项,git log --oneline
较多。 -
查看某个commit:
git show <commit id/branch/HEAD{x}>
-
查看远程仓库:
git remote (-v)
,其中常见结果有origin,是git给仓库服务器的默认名字。 -
添加远程仓库:
git remote add <shortname> <url>
,qizhong shortname就是url的简写,即远程仓库名。 -
删除远程仓库:
git remote remove <remote>
-
推送到远程仓库:
git push <remote> <branch>
(注意,push后,当有git commit --amend之后再想push,必须加上命令选项–force-with-lease才行)
3 分支操作
Git 的分支,其实本质上仅仅是指向提交对象(某次commit)的可变指针。
- 查看分支状态:
git branch (-v)
/git log --decorate
。 - 创建分支:
git branch <newbranchname>
。作用会在当前所在的提交对象上创建一个指针,只是创建了一个可以移动的新的指针。
Git 又是怎么知道当前在哪一个分支上?通过HEAD指针。在 Git中,HEAD是一个指针,指向当前所在的本地分支。将 HEAD 想象为当前分支的别名。
- 切换分支:
git checkout <branch name>
或git switch <branch name>
(v 2.23+),此时HEAD指针就会变了。如果<branch name>
=-
,那么切换回上一个分支。
在任何切换分支的时候注意:
(1)如果working tree是干净的(比如commit过),则切换分支时会修改working tree的内容,并且切换必定成功;
(2)如果working tree不是干净的,如修改过,无论是否执行git add,切换时有2中情况:
(i)如果切换去的分支是这次新建的或已经存在但其HEAD和当前分支相同,则git状态不变,必定成功;
(ii)如果切换过去的分支是已经存在的,则必须要先commit或stash才行。stash会还原工作目录。 - 新建分支并切换:
git checkout -b <newbranchname>
或git switch -c <newbranchname>
(v 2.23+)
新建分支需要注意的情况:(1)尽量在原分支commit了以后进行,否则会引起混乱。(2)可以用-C
来重置分支。 - 合并分支,本质上是分支指针的移动(指向对象的变化)。
git checkout <main_branch>; git merge <branch_to_be_merged>
。
合并分2种情况:
(1)无冲突合并。fast-forward(简单移动指针,无新commit)和recursive(自动创建合并提交,2个父提交).
(2)有冲突合并。手动合并后,add+commit。 - 合并之后撤销:(1)
git switch <进行merge的分支>
;(2)git reset --hard <想要的commit id>
- 删除分支:
git branch -d <branchname>
- 抓取数据:
git fetch <remote>
,从中抓取本地没有的数据并更新本地数据库,并且生成对应的不可修改的指针。当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本,也不会修改工作目录内容。 - 追踪分支。如上所说,不会有自动有新的分支(即使是git pull)。此时要
git checkout -b <local b> <remote/b>
来工作在远程分支上。如果分支名字相同,则git checkout --track <remote/branch>
或git checkout <branch>
(branch当前不存在且远程分支有且只有一个分支名字为branch)。 - 设置已有的本地分支追踪:
git branch -u <remote/branch>
- 查看:
git branch -vv
;查看所有git branch -a
;查看所有分支(本地和远程)和追踪状态:git branch -vva
- 拉取:
git pull
≈fetch+merge - 删除远程分支:
git push <remote> --delete <branch>
- 查看远程仓库及追踪信息:
git remote show <remote>
对于如何整合二个分支,可以(1)变基,(2)合并。变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。
-
git checkout <topic b>
;git rebase <base b>
;git checkout <base b>
;git merge <topic b>
使用变基or合并?总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史, 从不对已推送至别处的提交执行变基操作 -
给分支添加描述信息:
git switch <branch>
;git branch --edit-description
; 查看description用命令git config branch.<branch>.description
。
4 Git服务器
git有4种协议:(1)local(2)HTTP(3)SSH(4)Git。其中(1)local为同一主机上的另一个目录。push和pull时,用本地路径作为url。(2)http比较好。(3)ssh不支持匿名。(4)Git很少用,项目很大时才用,且不需要写时用。
-
搭建Git远程仓库。一般来说,可被push的仓库是裸仓库。需要用
git init --bare
来进行初始化(可在本地或服务器上,服务器上加上–share来分享权限)。其实Github上的仓库就是这样的裸仓库。裸仓库里没有工作目录,只有push的仓库的.git文件夹下的内容(不包含COMMIT_EDITMSG, index, logs)。 -
克隆得到的远程仓库:
git clone --bare <local git proj path> <remote git proj path>
-
如果要将远程仓库部署到服务器上,则用
scp -r <remote git proj path> user@server:/<path>
,之后别人就可以git clone这个git 项目了。
git push后,有以下消息:<oldref>…<newref> fromref -> toref
如果2个人对不同文件进行修改并分别push,Git不会在服务器上进行自动合并,而必须先在本地合并提交。也就是先fetch+merge <通常是(origin/master)>
5 Git工具
- Git引用日志:
git reflog
或git log -g <branch>
,记录了HEAD和分支引用的情况。用git show HEAD{<x>}
查看。 - 祖先提交:
HEAD^(x)
表示前一父提交的第x提交,HEAD~(x)
表示前x父提交的第一提交。合并分支时,当前的分支为第一父提交,被合并的分支为第二父提交。 - 区别某分支和另一分支的差别:
git log <branch1>..<branch2>
,表示在branch2种但不在branch1中的提交。 - 交互式暂存:
git add -i
,进行更细致操作。 - 贮藏:在还不想commit的时候,进行
git stash (push)
,可以将改动后的worktree存入栈中,worktree随后原始的样子。随后才能切换分支或者pull。可以在任何分支中git stash apply/pop (<name>)
来使用贮藏内容。不同的是apply不会删除本次贮藏,而pop直接删除。用git stash list
查看,用git stash drop <x>
删除某个贮藏,git stash clean
清空栈。 - 清理所有untracked文件:
git clean [-n] [-x]
,其中-n表示不真正执行,-x表示删除ignore的文件。 - 代码搜索:
git grep [option]
- 日志搜索:
git log -S <expr> (--oneline)
,在commit中搜索包含<expr>
增加或删减的对应commit。适用于查到关于相关变动。 - 修改提交:
git commit --amend
,会修改commit id。 - 合并、拆分、改写历史提交:
git rebase -i
- reset重置相关。reset后可以跟
(1)一个commit,表示当前分支引用和HEAD都指向了那个commit,通常是HEAD~;表示进行重置。重置到什么地步,是根据命令行选项来定的。
git reset --soft <>
只改变HEAD,index和working tree不变
git reset --mixed <>
改变了HEAD和index内容(这是默认效果)
git reset --hard/merge <>
,改变了HEAD、index和working tree
如果reset后接一个branch,那么当前分支会和branch同时指向branch所指向的同一个commit,HEAD指向当前分支。尽量不要这么做。
(2)路径(文件),即git reset <commit> <pathspec>
,相当于只能到–mix为止,并不能恢复working tree的内容。想要恢复working tree,需要用checkout。
reset还能用来改变提交历史。先reset,后进行相应的commit。 - checkout检出相关。对HEAD来说,只在不同分支上跳转,但不会移动分支的HEAD。
git checkout <branch>
类似reset --hard,但会尝试合并,不会直接覆盖。git checkout <commit> <file>
会检出commit的file到working tree。
总结如下:
- 中断合并
git merge --abort
就回到merge之前的状态(如果出现merge conflict) - 合并时保持某一边的内容:
git merge -Xours/-Xtheirs <branch>
。也可以合并单个文件:git merge-file --ours/theirs <file>
- 子模块
(1) 添加:git submodule add <url>
,git commit -am 'add submodule xxx'
,git push <origin> <master>
.
(2) 克隆含有子模块的仓库:git clone --recurse-submodules <url>
;如果克隆时没有加选项,就git submodule update --init --recursive
。 - 用git管理word和图片:在.gitattribute中加
*.docx diff=word
,加入配置git config diff.word.textconv docx2txt
,下载软件docx2txt,安装
#!/bin/bash
docx2txt.pl "$1" -
即可diff;管理图片可用exiftool,配置类比。
6 合集总结
创建一个仓库的流程:
- 在工作文件夹
git init
- 在远程文件夹
git init --bare
(或github) git remote add <repo_name> <url>
,其中url可以是本地路径- 编辑文件,如添加readme和.gitignore
git ls-files --others
:列出所有untracked文件git add $(git ls-files -o --exclude-standard)
:添加文件git commit
git push <repo_name> master
。如果在步骤2中没有–bare,则会出现如下错误:
remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: is denied, because it will make the index and work tree inconsistent
remote: with what you pushed, and will require ‘git reset --hard’ to match
remote: the work tree to HEAD.
remote:
xxxxxxxx
…
To /e/xxx
! [remote rejected] master -> master (branch is currently checked out)
error: failed to push some refs to ‘/e/xxx’
在github上创建一个全新的版本(无历史提交):
- 在本地
git checkout --orphan <new_branch>
git add -u
git push <remote> <new_branch>
在本地git merge的时候,出现了错误:fatal: refusing to merge unrelated histories。如图是059c到0d28没有“/”连接。解决方法:git merge <branch> --allow-unrelated-histories
。是因为git checkout --orphan
造成的(git switch --orphan和checkout --orphan效果不一样,要用checkout --orphan)。
对于远程的和本地的仓库和分支,有两种情况:
(1)将本地修改推到远程;
(2)将远程修改同步到本地;
- 对于第1种情况只需要git push即可。但注意此时
git branch -vv
是不会显示追踪关系的。 - 对于第2种情况,大多数情况用git pull,但有时,对于git branch -a不出现远程新的分支或者远程已经没有的分支在本地还有,这时就用
git remote update <remote> -p
更新本地的git分支,保持和远程分支一致。
关键在于区分哪个是变动的来源。
出现
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: ‘git pull …’) before pushing again.
hint: See the ‘Note about fast-forwards’ in ‘git push --help’ for details.
就要git pull
或 git push -f
关于git pull:
git pull
是自动抓取后合并该远程分支到当前分支,只能针对一个分支,也就是本地的当前分支。如果该分支没有上游分支(也就是没有追踪的分支),那么pull是失败的。会出现:
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
此时有2种方法:
git pull <remote_name> <branch_name>
,这种情况下可以pull,但是还是无法确立当前分支的追踪对象;
git branch --set-upstream-to=origin/<branch_name> <local_branch_name>
,才可以确定追踪对象。
如果想pull全部的分支,用git pull --all
.
关于git push
本地新建了分支,是没有办法直接git push
的,因为还没在远程创建分支。解决办法是git push --set-upstream <repo_name> <branch_name>
时常用git remote show <remote_repo_name>
来查看远程的分支的情况(本地是落后还是超前,是否有stale(意味着可能被他人删除))。如果有stale,本地也想拉取的话,就git pull -p / --prune
来删除本地对应的远程分支。
标签
查看所有标签:git tag -n
在当前commit打附注标签:git tag -a <tag_name> -m <message>
推送tag:git push <remote_repo> <tag_name>
重命名一个标签:
1, git tag <new_tag> <old_tag>
2, git tag -d <old_tag>
3, git push origin <new_tag> :<old_tag>
,其中冒号:表示删去。
如果要把一个分支a上的文件移植到另一个分支b上,那么使用命令
1, git switch <b>
2, git checkout --patch <a> <file>
detached head:处理方法
当执行git checkout <commit id>/<tag name>
,且这个commit不被任何分支指向的时候,那么就进入了一个“分离头指针(detached HEAD)”的状态。在“分离头指针”状态下,如果做了某些更改然后提交它们,标签不会发生变化, 但新提交将不属于任何分支,并且将无法访问,除非通过确切的提交哈希才能访问。 因此,如果需要进行更改,比如要修复旧版本中的错误,那么通常需要创建一个新分支:
git checkout -b <new branch>
github新建私有仓库并提交代码/克隆现有仓库,现在要token才行
一
在github新建私有仓库,如果直接git remote add origin https://github.com/<username>/<reponame>.git
,不会报错,但之后push会报错:
remote: Support for password authentication was removed on August 13, 2021.
remote: Please see https://docs.github.com/en/get-started/getting-started-with-git/about-remote-repositories#cloning-with-https-urls for information on currently recommended modes of authentication.
fatal: Authentication failed for 'https://github.com/<username>/<reponame>.git
错误原因:未用token绑定仓库
解决办法步骤:
1 在https://github.com/settings/tokens中generate token, 勾选所需的权限
2 git remote set-url origin https://<token>@github.com/<username>/<reponame>.git
3 push pull 和以前一样
二
对于无法直接clone private仓库一样,
git clone https://<token>@github.com/<username>/<reponame>.git
如果出现
git rm <xxx>
fatal: pathspec '<xxx>' did not match any files
那么说明,<xxx>
这个文件被ignore掉了,根本没被跟踪。确认是否在.gitignore文件里。随后直接用rm
删除。
如果出现错误
error: RPC failed; HTTP 408 curl 22 The requested URL returned error: 408
send-pack: unexpected disconnect while reading sideband packet
Writing objects: ...., done.
....
fatal: the remote end hung up unexpectedly
Everything up-to-date
那么是push的buffer不够。只需
git config http.postBuffer 524288000
如果在之前commit的时候包含了大文件,无法push
remote: error: Trace: xxx
remote: error: See https://gh.io/lfs for more information.
remote: error: File zzz is 390.07 MB; this exceeds GitHub's file size limit of 100.00 MB
remote: error: Filez zz is 390.55 MB; this exceeds GitHub's file size limit of 100.00 MB
remote: error: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com.
To https://github.com/xxxx
那么使用
git filter-branch --index-filter 'git rm --ignore-unmatch --cached <file name>' -- <commit>..HEAD/--all
然后push force:
git push origin --force (--all/--tags)
如果遇到错误:(目前还不知道原因)
error: object file .git/objects/xxx/xxxx is empty
fatal: loose object xxx (stored in .git/objects/xxx) is corrupt
运行以下命令即可(不会丢失未提交内容)
find .git/objects/ -size 0 -exec rm -f {
} \;
git fetch origin
如果还失败,运行
find .git/objects/ -size 0 -exec rm -f {
} \;
git symbolic-ref HEAD refs/heads/master
git fetch origin
如果想合并分支时,有选择性地从两个分支中选择有冲突文件(即不通过手动merge的方式)
首先进入主分支,git checkout branch1
,然后尝试合并git merge branch2
. 此时提示有confliction,auto-merging终止。
如果想要current contents,那么用
git checkout --ours <file_name>
如果想要accept incoming changes,那么用
git checkout --theirs <file_name>
然后git add <file name>
,commit。不需要用到git merge-file
。
如果在删除远程仓库时git push origin --delete xxx
,出现如下错误
error: unable to delete 'xxx': remote ref does not exist
error: failed to push some refs to 'github.com/xxxxxx'
那么很可能是别人执行了删除了远程的推送,自己本地还没删
解决办法:git fetch --prune