本文为读《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
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 <branch> -
推送到远程仓库:git push <remote> <branch>
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> = - ,那么切换回上一个分支。 - 新建分支:
git checkout -b <newbranchname> 或git switch -c <newbranchname> (v 2.23+) - 合并分支,本质上是分支指针的移动(指向对象的变化)。
git checkout <main_branch>; git merge <branch_t0_be_merged 。 合并分2种情况: (1)无冲突合并。fast-forward(简单移动指针,无新commit)和recursive(自动创建合并提交,2个父提交). (2)有冲突合并。手动合并后,add+commit。 - 删除分支:
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 pull ≈fetch+merge - 删除远程分支:
git push <remote> --delete <branch>
对于如何整合二个分支,可以(1)变基,(2)合并。变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。
git checkout <topic b> ; git rebase <base b> ;git checkout <base b> ; git merge <topic b>
使用变基or合并?总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史, 从不对已推送至别处的提交执行变基操作
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’
|