Git学习笔记

595 查看

一、Git起步

(1) 与其他版本控制系统的差别

(a) 存储信息的方式

  • 其他版本控制系统:以文件变更列表的方式存储信息;

  • Git:对小型文件系统的一组快照,每次提交制作快照时,若文件没有 修改则不重新存储,而只保留指向之前存储文件的链接;

(b) 本地执行

  • 其他版本控制系统:集中式,有网络延时,无网络时无法提交;

  • Git:本地执行,无网络时也可本地提交;

(c) 完整性

  • 其他版本控制系统:(待确定);

  • Git:SHA-1校验和;

(2) 三种状态

  • 已提交(committed):数据已经安全的保存在本地数据库中;

  • 已修改(modified):修改了文件,但还没保存到数据库中;

  • 已暂存(staged):表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中;

(3) 三个工作区域

  • Git仓库:保存项目元数据和对象数据库的地方;

  • 工作目录:项目的某个版本独立提取出来的内容;

  • 暂存区域:一个文件,保存了下次将提交的文件列表信息(修改过的文件信息),也被称为“索引”;

(4) 配置

(a) 配置存放位置

  • /etc/gitconfig文件,使用--system选项,系统上每个用户通用配置;

  • ~/.gitconfig或~/.config/git/config文件,使用--global选项,针对当前用户;

  • 当前仓库.git/config,针对当前仓库;

(b) git config:

  • 修改git config [--global|--system] KEY VALUE

  • 查看所有git config —list

  • 查看某一项git config KEY

  • 别名git config [--global]alias.SHORT_COMMOND COMPLETE_COMMOND
    git config --global alias.last 'log -1 HEAD'

二、Git初始化

(1) 获取Git仓库

  • 现有所有文件导入git中:git init

  • 克隆现有的仓库:git clone URL [NEW_NAME]

三、Git基本提交

(1) 文件状态

状态变化图(见右)

  • 未跟踪;

  • 已跟踪:

    • 未修改(已提交);

    • 已修改;

    • 已暂存;

(2) 检查当前文件状态

git status [--short(-s)]

状态简览

  • ?? -- 未跟踪;

  • A -- 新添加到暂存区;

  • D -- 删除的文件;

  • 左边M -- 被修改并添加到暂存区(修改过,未add的无法提交);

  • 右边M -- 在工作区被修改了还没放入暂存区;

(2) 跟踪文件

(a) add

理解为“添加内容到下一次提交中”而不是“将一个文件添加到项目中”

git add FILE_NAME | DICTIONARY_NAME |.

参数:

  • -i:交互方式,可选择git基本功能;

  • -u:更新已经加入到暂存区的文件修改;

(b) 忽略文件:

.gitignore文件

规则:

  • 注释:“#”开头;

  • 防止递归:以(/)开头;

  • 指定目录:以(/)结尾;

  • 忽略:模式前加上惊叹号(!)取反;

  • glob 模式匹配:

    • 匹配零个或多个任意字符:*;

    • 匹配任意中间目录:**;

    • 匹配任何一个方括号中的字符:[xxx];

    • 匹配任何一个范围的字符:[x-x'];

    • 匹配任意一个字符:?;

实用工具:http://gitignore.io

(3) 提交更新

git commit [-m | -a ] COMMIT_MESSAGE

COMMIT_MESSAGE多行信息,可用一个单引号实现
参数:

  • -m:可以简要填写提交信息;

  • a:自动暂存已跟踪过的文件,并提交;

(a) 补充提交

git commit --amend [-m NEW_MESSAGE]

提交之后,想重新提交刚才提交过的更新,用此命令;

用参数“-m”可以更新提交的信息;

(b) 提交的过程

  • git会保存一个提交对象,保持暂存内容快照的指针,以及提交时的信息;提交对象的父对象,是上一次提交的对象(可能多个或没有);

  • 暂存操作为每个文件计算校验和,保存当前版本的文件快照,等待提交;

  • 提交时,计算每个子目录的校验和,并将其保存为树对象;创建提交对象,包含指向树对象和各个文件快照的指针;非首次提交有指向父对象的指针;

(4) 移除文件

git rm [-f | --cached] FILE_NAME | DICTIONARY_NAME

参数:

  • -f:删除已修改并放入暂存区的文件;

  • -cached:将文件从暂存区(git仓库)中移除,但仍保留在当前工作目录中;

(5) 移动文件

git mv FILE_FROM FILE_TO

(6) 储藏与清理

(a) 储藏 stash

  • 解决:要切换分支,但又不想为做了一半的工作创建一次提交的情况;

  • 储藏git stash [save MESSAGE|-u(--include-untracked)|-k(--keep-index)|--all(-a)]

    • 默认储藏已暂存的修改;

    • u,则在此基础上加上未暂存的修改;

    • k,--keep-index则只储藏未暂存的修改;

    • --all,-a所有文件储藏,不仅仅本次修改;

  • 查看git stash list

  • 恢复第n次储藏(与list中的数字一样),默认最近一次即stash@{0}:git stash apply stash@{n}
    恢复最近一次也可以用(但会删除储藏的记录):git stash pop

  • 移除储藏git stash drop stash@{n}

  • 以储藏新建分支git stash branch

(b) 清理 clean

移除不在暂存区的修改

git clean [-f|-d|-n]

-f 强制执行,-n 列出将会做的处理

四、还原历史

(1) 重置

(a) “三棵树”

  • HEAD:当前分支引用的指针,上一次提交;

  • Index:暂存区域引用指针,预期的下一次提交;

  • Working Directory:沙盒;

(b) reset

git reset --soft|mixed|hard
  • soft:移动HEAD到目标提交上,之间修改保留在暂存区;

  • mixed(默认):除了以上操作,将Index(暂存区)上的还原成目标提交上的,之间修改保留在工作区;

  • hard:除了以上两项操作,将工作区的修改还原成目标提交上的;

与checkout一样,区别在于:checkout更安全有检查,且移动HEAD(分支)而非其指向;

(2) 撤销提交

(a) 取消暂存的文件

将已加到暂存区的文件取消暂存

git reset HEAD FILE_NAME
git reset -- FILE_NAME

(b) 撤销对文件的修改

  • 撤销所有文件修改,回到上一次提交状态:git reset --hard HEAD

  • 撤销不在暂存区中的文件的修改:git checkout — FILE_NAME;

  • 将文件还原到BRANCH_NAME分支上的最新进度:git checkout BRANCH_NAME — FILE_NAME

(c) 合并还原提交

git revert -m N BRANCH_NAME | COMMIT_HASH

撤销该次提交的操作,该次提交之后的修改仍然存在

若要撤销“还原提交”操作,继续用revert命令;

git revert -n

撤销而不产生提交信息,可查看做了哪些更改,再决定是否提交

(d) 重写提交历史

  • 利用变基操作:

    • git rebase -i BRANCH_NAME | HEAD~n | ...

    • 之后,有从当前分支到BRANCH_NAME分支之间的提交历史列表,选择pick(提交)或squash(合并)等操作;

  • 利用reset:git reset --soft COMMIT_HASH,再进行提交 git commit

五、分支与合并

(1) 分支

  • 实际上是指向提交对象的可变指针,默认是master;

  • HEAD指针指向当前所在的本地分支;

(2) 新建分支

(a) 根据branch新建

git checkout -b [--track][NEW_BRANCH_NAME] BRANCH_NAME
# 等同于
git branch BRANCH_NAME
git checkout BRANCH_NAME

参数--track:将新分支与当前分支建立跟踪关系

(b) 根据commit新建:

git checkout COMMIT_HASH
git checkout -b NEW_BRANCH_NAME
# 或者
git branch NEW_BRANCH_NAMECOMMIT_HASH

(3) 查看分支

git branch [-v | --merged | --no-merged]

参数

-v:查看每个分支的最后一次提交;

--merged,--no-merged:查看已合并、未合并到当前分支的分支;

(4) 删除分支

git branch -d | -D BRANCH_NAME

-D是丢掉未合并工作,强制删除

(5) 分支合并

git merge BRANCH_NAME

将BRANCH_NAME合并到当前分支上

若有冲突,git status, git diff 查看冲突并解决,可用git mergetool

中止合并git merge —abort
"fast-forward":即如果当前HEAD是待合并commit的祖先,则会将当前分支指针指向待合并commit,不会产生新的merge commit,这是git merge操作的默认操作;
若加上参数git merge --no-ff,则在此场景下,会产生新的merge commit;

(6) 变基

(a) 方法

使用rebase命令,可以将提交到某一分支上的所有修改都移至另一分支;

git checkout BRANCH_C4
git rebase BARNCH_C3
git checkout BRANCH_C3
git merge BRANCH_C4

# 等同于
git rebase BRANCH_C3 BRANCH_4
git checkout BRANCH_C3
git merge BRANCH_C4

变基过程中,若存在冲突则解决冲突,后用git rebase —continue,而不是git commit

(b) 原理

首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master)的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用;

(c) 注意

不要对在你的仓库外有副本的分支执行变基;

(7) 打标签

(a) 列出标签

git tag [-l GLOB_STRING | -n]

参数

  • -l:列出符合模式的所有标签;

  • -n:列出所有标签即对应的标签信息(若无标签信息则为commit信息)

(b) 查看标签

git show TAG_NAME

(c) 创建标签

  • 附注标签:

    git tag -a TAG_NAME -m TAG_MASSAGE

存储了标签的作者、时间等信息;

  • 轻量标签:

    git tag TAG_NAME

将提交校验和存储到一个文件中,不存其他信息;

  • 后期打标签:

    git tag -a TAG_NAME HASH

给某一次提交打标签

(d) 删除标签

git tag -d TAG_NAME

(e) 删除远程标签

git push origin :TAG_NAME

(f) 推送标签

git push origin TAG_NAME | --tags

将特定标签推送到服务器,推送所有标签

(g) 检出标签

git checkout -b BRANCH_NAME TAG_NAME

在特定的标签上创建一个分支

六、远程协作

(1) 远程仓库

(a) 查看远程仓库

git remote [-v | show REMOTE_NAME]

参数

  • 无参数:列出远程服务器的简写;

  • -v:需要读写远程仓库的简写与其对应的URL;

  • show:列出远程仓库的URL与跟踪分支的信息,以及push和pull的情况;

(b) 添加远程仓库

git remote add [-u] SHORT_NAME URL

可使用SHORT_NAME来代替整个URL,参数-u设置为默认远程仓库与--set-upstream一样

(c) 拉取远程仓库

  • 仅拉取:

    git fetch REMOTE_NAME
    • 访问远程仓库,从中拉取所有本地还没有的数据,但不会合并,需手动merge;

    • 如果使用clone,会自动将其添加为远程仓库并简称为origin;

  • 拉取并尝试合并:

    git pull REMOTE_NAME BRANCH_NAME
    • 拉取后自动尝试合并到当前分支;

(d) 推送到远程仓库

git push REMOTE_NAME LOCAL_BRANCH_NAME:REMOTE_BRANCH_NAME
  • git pull --ff-only:类似的,可以在pull操作时加上参数--ff-only,只合并那些“fast-forward”的情况;

  • git pull --rebase COMMIT:不会产生新的merge提交,而是和rebase类似,将两个提交之间相差的提交部分提交到COMMIT之后;

  • git push REMOTE_NAME +LOCAL_BRANCH:REMOTE_BRANCH:强制更新远程分支,如果本地reset之后,可以采用这个命名更新远程分支;

(e) 重命名远程仓库

git remote rename OLD_REMOTE_NAME NEW_REMOTE_NAME

(f) 删除远程仓库

git remote rm REMOTE_NAME

(2) 远程分支

(a) 查看远程分支

git ls-remote REMOTE_NAME
git remote show REMOTE_NAME
git branch -r

# 所有跟踪分支
git branch -vv

(b) 增加远程仓库

git remote add REMOTE_NAME REMOTE_URL

(c) 跟踪分支

# 从远程拉取分支并跟踪
git checkout --track ORITIN_NAME/BRANCH_NAME

# 分支A跟踪分支B,分支B若有跟踪远程分支,则分支A也跟踪了 
git checkout --track -b BRANCH_NAME_ABRANCH_NAME_B    

(d) 删除分支

git push ORIGIN_NAME --delete BRANCH_NAME

七、检查与比较

(1) 比较文件

git diff [--staged(--cached) | HEAD]

参数

  • 无参数:比较工作区与暂存区快照之间的差异;

  • -staged或--cached:已暂存的与HEAD的差异;

  • HEAD:比较工作区与HEAD的差异;

(2) 查看冲突

git diff [--ours | --theirs | --base]
  • 以该分支为基础查看,合并了什么:--ours;

  • 以对方分支为基础查看,合并了什么:--theirs;

  • 查看冲突的改动:--base;

(3) 查看提交历史

git log [-p | -RECENT_N_TIMES | --stat |--pretty | --graph | --since | --before | --author | --committer | --grep |--decorate]

参数

  • -p:显示每次提交的内容差异;

  • RECENT_N_TIMES: 最近N次提交;

  • -stat:每次提交的简略统计信息(统计文件哪些行被修改了);

  • -pretty=oneline|format:"..."

    • oneline:单行显示;

    • format:可用格式占位符 %H, %h 长短Hash字串,%an 作者名字,% cn 提交者名字,%cd 提交日期,%s提交说明;

  • -since(--after),--before(--until):限定日期,如“2008-10-01”;

  • -grep:仅显示指定关键字的提交;

  • -decorate:提交历史中列出分支信息;

git log --oneline --graph --decorate
一行查看日志,有branch合并的信息

git log -p FILE_NAME
显示文件FILE_NAME,每次提交实际修改的内容

git log -L BEGIN_LINE,END_LINE:FILE_NAME
指定文件中的某些行,查看这些行的变化日志

(4) 提交日志查看工具

(a) 引用日志

git reflog

与log的不同在于:最新更改放在列表前面显示,包括reset重置操作;(重置后也能查看到跳过的commit信息)

每当HEAD所指向的位置发生了变化,Git就会将这个信息存储到引用日志这个历史记录里;

(b) 查看具体修改 git show

  • 查看分支在n次前所指向的提交:

    git show BRANCH_NAME@{n}
  • 父提交:

    git show HASH^
    git show HASH~
  • 祖父提交:

    git show HASH^^ 
    git show HASH~2
  • (合并的)第二父提交:

    git show HASH^2

(c) 提交区间

# 在分支A中,但不在分支B中的
git log BRANCH_B..BRANCH_A
git log ^BRANCH_B BRANCH_A
git log BRANCH_A --not BRANCH_B

# 只被两者之一包含
git log BRANCH_A...BRANCH_B

(d) 搜索

  • 搜索目录文件:从提交历史或工作目录中查找一个字符串正则表达式;

    git grep [-n | --count] FIND_GREP

参数

    • n:输出说找到匹配行行号;

    • -count:输出概述,仅包括哪些文件包含匹配及匹配数量;

    • 搜索日志

      git log -S FIND_GREP --oneline
    • 搜索文件的每一行来源

      git blame FILENAME