git 沙河游戏节点图, 自由沙盒模拟git, 各类交互git命令

 


git学习练习总资源链接: https://try.github.io/ (练习已通,有document)

本沙盒游戏教学:https://learngitbranching.js.org/?demo 

自由沙盒模拟网页 : http://git-school.github.io/visualizing-git/

 

好的译文: https://github.com/geeeeeeeeek/git-recipes/wiki


 

什么是git? 

一个分布式的源代码库。管理Linux内核源代码。

 git已快照形式保存和处理内容,每一个提交都是一次快照。git可以在快照之间回滚。

 


 

一个节点代表一个commit.

*代表当前分支的最后一次提交:HEAD

master是主干。

其他名字是分支。 

 

git merge :用于合并分支的代码。

git rebase : 线性合并分支:

 

git rebase [-i] [目标] [移动记录] 

git rebaes [目标]  #省略[要移动的记录],则为当前分支的所有commit。

 

假如当前分支是bugFix:

  1. git rebase master. 这样bugFix分支就相当于在master的基础上新增的代码了。
  2. git checkout master 回到master
  3. git rebase bugFix,  master和bugFix的代码都一样了。

 

HEAD:

是一个对当前检出记录的符号引用 -- 也就是指向你正在其基础上进行工作的提交记录

它总是指向当前分支上最后一次的提交记录。 大多数提交树的git命令都是从改变HEAD的指向开始的。

⚠️,后面章节讲的远程分支 origin/master是例外

 

HEAD 通常是指向分支名的(如bugFix)。在你提交commit时,改变了分支的状态,这一变化通过HEAD变得可见。

 

分离的HEAD:

让它指向某个具体的提交记录(hash值)而不是分支名。 

git checkout <hash> 

git checkout命令本质就是移动HEAD,到目标commit点, 然后更新工作目录以匹配这个commit点。

因为这个操作会overwrite local changes,导致改变的文件丢失,所以Git强迫你先commit或stash工作目录中的改变的文件。

⮀ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
    app/assets/javascripts/search.js
Please commit your changes or stash them before you switch branches.

 


 

关于git stash  (具体工作原理和全部的知识见链接文章)

会把尚未加入stage的文件和statge中的文件保存(就是未commited的文件),以便在之后使用。

之后可以revert them from your working copy.

 

现在可以进入任何其他操作,如创建新commits, 转变分支,执行其他git操作了。

⚠️stash是本地的。当你push的时候,stash不会被传输。

 

Re-applying your stashed changes:

$ git stash pop

 

另外使用git stash apply, 可以reapply the changes的同时在stash中保留它们。这在为多个分支应用时有用。

⚠️:默认Git不会stash 未tracked文件和ignored files。

 

 

 


 

相对引用 

 

通过指定提交记录hash值的方式在Git中移动不方便操作。

必须用到git log, 而且hash值非常长。

因此可以只使用前几个字符代表一个提交记录 , 即“相对引用”。

^    表👆向上移动一个commit记录。

~2 代表向上移动2个提交记录,~5,代表移动5个提交记录

使用git checkout HEAD^, 就代表向上移动一次。

 


 

强制修改分支位置--移动分支

 

git branch -f master HEAD~3

代表把master向上移动三个提交节点,即第3个father note 

-f :代表--force, force creation, move/rename, deletion 

 


 

撤销变更

  • git reset
  • git revert 

 

CommandScopeCommon use cases
git reset Commit-level Discard commits in a private branch or throw away uncommited changes
git reset File-level Unstage a file
git checkout Commit-level Switch between branches or inspect old snapshots
git checkout File-level Discard changes in the working directory
git revert Commit-level Undo commits in a public branch
git revert File-level (N/A)

 

Git Reset

原文:

takes a specified commit and resets the "three trees" to match 
the state of the repository at that specified commit.

Three Trees

 

把分支回退指定个数的commit记录,来实现撤销改动。相当于使用时间机器回退到过去开发的阶段。(撤销的提交记录还存在,只是未加入stage暂存区) 

⚠️ 这条命令对团队使用的远程分支无效!

例子:

# 从当前工作目录回退一个commit.
git reset HEAD~

# 从当前工作目录回退2个commit
git reset HEAD~2 

⚠️被回退的2个commit提交变成悬挂提交。下次Git执行垃圾回收时,这两个提交会被删除!

 

git reset可以把stage中的文件拿出stage。git reset </filename>

⭠ autoquery± ⮀ git reset README.md
Unstaged changes after reset:
M    README.md

 

 

三个模式选项

  • --soft -stage缓存区和工作目录都不会改变。
  • --mixed默认选项。✅
    • 缓冲区和你指定的提交同步a(在提交a后,加入缓存区的文件被拿出来了)
    • 工作目录不受影响(在提交a后对工作目录中的文件进行的改变被保留!)。
  • --hard -缓存区和工作目录都被更新,以匹配指定的commit点a。
    • 相当于回退到刚刚提交完a的状态! 
    • 在提交a后的操作全部删除,包括工作目录中对文件的改变,相当于时光倒流

可以认为这个三个模式是对a git reset操作的作用域的定义!

一般使用默认的--mixed。

 


 

Git Revert (点击见详细)

专门用于撤销远程提交记录。但本质上是新增一个commit记录,但更改了code,去掉了之前那个commit记录中的变更代码。

c0-c1-c2(->c3)

git revert C1, 结果是新增了一个c3. 

c3是c1的反转操作,即c1中变化的代码,在c3中被撤销了。

 

例如, 你追踪一个bug并发现它是在某个commit点a内增加的一个变量。你无需手动进入这个commit点,删除这个变量,然后再committing一个新的snapshot,你直接使用git revert a命令自动为你做上面的事情。 

⚠️:如果你revert提交点c1, 但是c2中有对c1中变化的代码的进一步修改,你不能使用git revert c1, 系统会提示你冲突,需要先搞定冲突代码。

 

How it works

git revert命令用于撤销一个仓库的commit历史中的某个变化。

其他撤销命令如git checkout 和 git reset,移动HEAD和branch ref pointers到一个指定的commit点。

Git revert也take a specified commit,但是,git revert不会移动ref pointers到这个commit点。

而是执行一个反转操作,反转那个commit的改变的代码,并创建一个新的“revert commit”。

最后ref pointers 会更新,指向这个新的"revert commit", 让这个commit成为分支的端点tip。

 

选项

-e  --edit (默认选项) 打开系统的编辑器,提示你编辑commit信息。

-n --no-commit (一个特别的选项)使用它,git revert不会创建新的commit, 只会在working directory反转变化的代码,并在缓存中加入反转代码后的文件。一句话理解:需要你手动提交!其他没变化。

 

和get reset的比较:

1:不会改变历史记录。

  • 基于这个优点git revert可以在一个public branch上使用,
  • 而git reset最好在个人的branch上进行操作。

2:git revert只改变单一的提交点。而git reset会从当前提交往回退(回到过去)。

可以这么理解:

  • git revert是撤销committed changes的工具

⚠️git revert也和git checkout类似,在revert操作期间会重写工作目录中的文件。所以它会要求你commit/stash changes。

 


 

 

File-level Operations

git reset和git checkout命令可以接收一个file path作为参数。这会强制把它们的操作限制到一个单一文件。

例子:

git reset HEAD~2 foo.py

 

当引用一个文件路径时, git reset更新缓存区以匹配特定commit点的版本的指定文件。

👆的命令会取得在提前两个提交点的版本的foo.py文件,并把它放入Staged files缓存区中。

但是工作区出现对应的文件的unstaged 状态:

⭠ autoquery ⮀ git reset head~3  README.md
Unstaged changes after reset:
M    README.md
⭠ autoquery± ⮀ git status

On branch autoquery
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   README.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md

 

我的理解:

  1. 执行git reset head~2 foo.py命令
  2. 取得的文件foo.py放入缓存区,但程序发现取出的文件和当前的foo.py文件的内容不一致,所以出现👆的情况。

另外,如果我修改了上一个commit点的foo.py文件并放入缓存区。

然后又执行git reset head~2 foo.py

程序发现取出的文件和当前修改的foo.py文件的内容不一致,会把当前修改的foo.py文件放入工作目录区。

⚠️复杂的操作尽量配合可视辅助工具Sourcetree

 

 

Git checkout head File命令

它会把file放入working directory工作目录。

⚠️我的实际操作是使用此条命令后,取出的文件会放入到缓存区!我的理解:

  1. git checkout head File
  2. 程序自动把取出的文件执行git add命令,放入缓存区。
git checkout HEAD~2 foo.py

 

⚠️此时git checkout命令不会移动HEAD标签,即你不会切换分支!

⚠️:如果把这个变化git commit就相当于直接执行一个git revert命令了!!

 

 

 

 

 


 

整体提交记录 

当开发人员说,我想把这个commit放到这里,那个commit放到刚才的提交的后面,就可以使用:

将一些commit复制到当前位置HEAD下面的话,使用这个命令: 

git cherry-pick <提交号>...

 

⚠️个人理解,是每个commit应该是一个独立的模块。

⚠️,前提是你知道你想要的commit记录的hash值,才行。如果不知道,往下看⬇️

 

 


 

 

交互式的rebase 

 

git rebase -i HEAD~3

指带参数 --interactive的rebase命令,简写-i

 

rebase会打开一个UI界面:

 

 


 

git 技巧 1: 本地stage提交

 

假如当前在bugFix分支(C4), 而master在C1, 希望只把C4合并到master上。

c1(master)-c2-c3-c4(bigFix*) 

第一种办法:

git checkout master#HEAD回到master 

git cherry-pick C4#ok了

第二种办法:

git rebase -i C1 #会打开UI界面,选择C4,去掉C2, C3 ,确定。

#这时buFix是当前分支它包含C4直接放到了C1下面。

git checkout master #回到master, 因为master的数据比较旧

git merge bugFix#合并分支。 

 

 


 

git 技巧 2: 

 

git commit --amend

修复最新提交的便捷方式。作用是:

将缓存的修改和之前的commit合并到一起,生成一个新的提交并替换掉原来的提交。

这是从写项目历史的命令。

 

讨论:

仓促的提交在你日常开发过程中时常会发生。很容易就忘记了缓存一个文件或者弄错了提交信息的格式。--amend 标记是修复这些小意外的便捷方式。

 

注意:⚠️

不要修复public commit! 永远不要重设和其他开发者共享的commit。

修复也一样:永远不要修复一个已经推送到公共仓库的commit!

 https://learngitbranching.js.org/?demo (点击链接看演示, 然后选择第四行第2个按钮)

c1(master)—c2(newImage)—c3(caption*) 

设计师需要在c2上调整图片格式,如何做?

|再问:

设计师干嘛不在C3上调整?

答:猜测C2提交记录是针对图片的设计,c3提交记录是针对其他设计。

假设:

git checkout newImage

git commit --amend

git checkout capiton

git merge newImage #这会导致新产生一条commit记录。C4, 不符合线性的要求。

 

而使用:

#  调整C2, C3的位置,让C2位于分支tip, 因为git commit --amend用于最新提交点。

git rebase -i C1   

# 对C2进行修改

git commit --amend

#再调整回原先的结构。

git rebase -i C1 

#相当于,历史commit记录树没有发生变化。 

#然后就可以合并了
git checkout master
git checkout caption

 

 

⚠️,作者提示这可能会导致冲突:改用挑🍒,更快捷。cherry-pick

git checkout newImage

git commit --amend      #这会出现一个分叉。

git checkout master

git cherry-pick C2' C3

 


 

标签的作用:tag

 

给某个提交记录一个标签,类似⚓️。 用于重要版本的标记。 

git tag V1 <hash> 

如果不指定<hash>提交记录,则标记到HEAD指向的位置,因此可以写两条语法:

git checkout <hash>

git tag V1 

 

Git Describe

用来找到最近的tag。帮助你在commit record的历史中移动了多次后找到方向。

git describe <ref>

<ref>是任何识别commit记录的引用,不指定的话,则以当前HEAD位置为准。

 

它的输出结构:

<tag>_<numCommits>_g<hash>

解释:

<tag>是离<ref>最近的标签,

numCommits是表示和<ref>相差多少个commit记录,

hash表示的是你所给定的<ref>所表示的提交记录hash值的前几位。 

 

Pushing tags

默认,标签不会自动推送上去。 --tags将你所有的本地标签推送到远程仓库。

git push <remoteName> <TagName>

push all tags:

git push <remoteName> --tags

 


 

挑战1:线性移动合并分支:git rebase  (可以看sandbox案例)

 

git rebase [-i] [目标] [要移动的记录] 

git rebaes [目标] #省略[要移动的记录],则为当前HEAD 


 

挑战2: 使用HEAD~和HEAD^2来移动HEAD的位置 

相对引用的扩展:

^2代表第二个父引用记录,可以链式使用。

git checkout HEAD~^2~

 表示上一个父记录,然后再第二个父记录,然后再上一个父记录。

 

如果要在这里建立一个新分支

git branch bugFix HEAD~^2~ 

 

 


 

挑战3 ,git rebase 的再次使用。

HEAD也可以做[目标] 

git rebase [-i] [目标] [要移动的记录] 

 



 

remote repertory

 

简单来说就是你的仓库在其他机器上的备份。

特点:

  • 备份,恢复丢失数据
  • 远程让代码可以社交化了!其他人可以为你的代码做贡献。 

git clone: 在本地创建一个远程仓库的拷贝 

  


 

 

远程跟踪分支 remote-tracking branch

  

在本地仓库多了一个名为o/master的分支,这种类型的分支叫做 远程跟踪分支。

远程跟踪分支反应了远程仓库(在你上次和它通信时)的状态。

这有助于理解你本地的工作和公共工作的差别 -- 这是和别人分享工作成果最重要的一步。 

 

特别的属性:在你checkout时,自动进入分离的HEAD状态。 原因是Git要求,不能在远程跟踪分支上直接写代码,需要先在其他地方写好代码后,更新到远程仓库对应的位置,远程跟踪分支才会更新。

 

git checkout o/master;

git commit;

在有新的commit提交记录后,o/master不会同步更新,会和HEAD分离。

⚠️,Head总是指向当前分支上最后一次的提交记录,但这里是例外。

o/master只有在远程仓库中对应的分支更新后,才会更新。 

 

格式:<remote name>/<branch name> 

简称: o/ 

默认: remote name 是 origin

 


 

Git Fetch 

 

从remote repertory获得数据。

当从远程仓库获得数据时, 远程分支会自动更新,以反应最新的远程仓库。 

 

git fetch会做的事情: 

  • 从远程仓库下载本地仓库缺少的commit记录
  •  更新远程跟踪分支 如 origin/feature_branch
git fetch不会做的事情:
  • ⚠️不会更新你的master分支和其他分支,也不会修改你磁盘上的文件。
  • 因此,不是说git fetch后本地仓库就和远程仓库同步了,这是❌的想法。
  • git fetch只是单纯的下载服务。 
//取指定的分支或tags,下载所有需要的commits和文件
git fetch <repository> [<refspec>...]

       
// 取所有remote branch
git fetch --all [<options>]

// 试运行,就是一次彩排,看看会出现什么结果
git fetch --dry-run

 

 

使用git fetch 同步远程仓库:

git fetch origin

//会显示我们下载的branchs
//a1e8fb5..45e66a4 master -> origin/master
//a1e8fb5..9e8ab1c develop -> origin/develop
//* [new branch] some-feature -> origin/some-feature

如果想要查看上游master增加了什么commit,可以运行git log命令,并使用origin/master进行检索:

git log --oneline master..origin/master

 

然后批准这些变化并合并它们到你的本地master分支:

git checkout master
git log origin/master

//现在origin/master和master分支指向同一个commit点了,
//并且你和上游upstream 开发同步了。
git merge origin
/master

 


 

Git Pull

 

下载下来后,把远程分支合并到本地的方法: 

  • git cherry-pick o/master
  • git rebase o/master
  • git merge o/master
  • 等等 

 

 git pull 就是直接一步完成2个命令。即git fetch和git merge的方便代码。

 等同于:git fetch 和git cherry-pick o/master

 效果等同于:git fetch, git rebase master o/master, git rebase o/master master。但结构是线性的,因为使用git rebase是线性合并,o/master会指向新C3' 

 


 

模拟团队合作:下载。

  1. git fetch 
  2. 在本地的master分支新增了commit
  3. git merge o/master, 合并远程分支到本地的master。

 


 

Git Push

 

当本地仓库被修改,需要执行git push操作来分享这个修改的代码给team members:

git push <remote> <branch>
git push <remote> -all

 

如果不带任何参数,会使用push.default的设置。他的默认值是正在使用的Git的版本,推送前最好检查一下这个配置 

  


  

偏离的困惑 

 

假如周一你克隆了一个仓库,然后开发某个新功能。到周五时,可以提交到远程仓库了。但是,

这周你的同事写了一堆代码并修改了你还在使用的API。这些变动让你的新开发的功能不可用。

但他已经提交推送到远程仓库了。你的工作变成了基于旧版本的代码,已和远程仓库的最新的代码不匹配了。

 

这时,Git不会允许你push,它会要求你先合并远程最新的代码,然后你才能分享你的工作。

这就是历史偏移 

 

需要:

  1. git fetch
  2. git rebae o/master#这里可能你的新增功能,会失效,
  3. 你需要先修改代码,然后commit。最后:
  4. git push 
还可以使用git merge
  1. git fetch#更新本地仓库中的远程分支
  2. git merge o/master#这会产生新的commit记录。C4
  3. git push 

 

--rebase选项: 

git pull --rebase , 等同于用git rebase合并远程分支,而默认是git merge 

 

小结:工作流程: fetch, rebase/merge, push

 

强制push

Git 为了防止你覆盖中央仓库的历史,会拒绝你会导致非快速向前合并的推送请求。

--force 这个标记覆盖了这个行为,让远程仓库的分支符合你的本地分支,删除你上次 pull 之后可能的上游更改。

只有当你意识到你刚刚共享的提交不正确,并用 git commit --amend 或者交互式 rebase 修复之后,你才需要用到强制推送。

⚠️ 但是,你必须绝对确定在你使用 --force 标记前你的同事们都没有 pull 这些提交。

 


 


 

关于origin和它的周边  --Git 远程仓库高级操作 

 

推送push 主分支

 

大型项目,开发人员会在特性分支上工作,工作完成后只做一次集成。

但有些开发者只在master上push,pull。这样master总是最新的,始终与远程分支保持一致。

 

  • 将特性分支集成到master上。
  • push并更新远程分支 

  


 

为什么操作远程分支不喜欢用merge, 见仁见智

 

  •  喜欢干净的提交树,用rebase
  •  喜欢保留提交历史的,用merge

 


 

远程跟踪分支:remote-tracking branches

 

Git 设置了master和o/master的关联。 

  • pull时,commit记录会被先下载到远程分支,如:origin/master上,之后会再合并到master分支。
  • push时,我们把工作从master推到远程仓库中的master分支,同时更新远程分支origin/master。

 

它们的关联关系,是由"remote tracking" 属性决定的。

master被设定为跟踪o/master。

 

当你克隆远程仓库时,Git会为远程仓库的每个分支都设定一个远程分支,然后再在本地创建一个和远程仓库

中的分支一样的本地分支。

 

克隆完成后,你会得到本地分支,如果没有就是空白。

 

这也解释了在克隆时会看到下面的输出:

local branch "master" set to track remote branch "o/master" 

本地分支“master”设置跟踪远程分支 "o/master"

 


 

可以指定"remote tracking" 属性属性

 

让任意分支跟踪o/master,然后该分支就会像master分支一样得到隐藏的push目的地和merge的目标。

这意味着你可以在分支XXX上指向git push, 将工作推送到远程仓库的master分支上。

两种设置方法:

 

  • git checkout -b XXX o/master
  • 如果已经有了XXX分支,则使用git branch -u o/master XXX 
-u的意思:-u, --set-upstream-to <upstream>

  

 


 

Git push的参数

 

默认Git通过当前checkout分支的属性来确定远程仓库和要push的目的地。

 

我们也可以明确指定push的参数: 

git push <options> <remote-Repository-name> <place> 

例子: 

git push --set-upstream origin master:

把当前分支push, 并设置远程仓库origin为upstream。

 

解释:

这行代码一般用在创建一个远程链接并同步上传数据:

⭠ master ⮀ git remote add origin https://github.com/xxxxxx/yyyyyy.git
//链接远程仓库origin.
⭠ master ⮀ git push -u origin master //Branch master set up to track remote branch master from origin. //本地分支master已经开始追踪远程仓库origin的master分支!

 

<place> 详细解释: 

可以分解为<localbranch-source>:<destination> 

即本地分支source,提交到远程的另一个分支destination。

<source>可以是任何commit记录位置。 

如果<destination>在远程仓库中并不存在,则会在远程仓库中新建这个分支。

例子: git push origin localbranch:remotebranch

 

强制执行一次non-fast-forward merge

git push origin --force 

⚠️,只有绝对确定你正在做的才这么用!

 

--all选项:推送所有分支:

git push <remote> --all

 

把标签也推送上去,默认tags是不推送的。

git push <remote> --tags

 

Amended force push

git commit --amend 用于更新提交点,

本质就是把commit原先的改变和更新的内容存入一个新的commit点,扔掉原来的commit点。

 

但是如此,git push 会导致失败,因为Git会发现Amended的commit点和远程的commit点的内容是分离的。

 

此时需要使用--force选项来push一个amended commit!

# make changes to a repo and git add
git commit --amend
#此时如果直接git push 会报告错误❌:
# ! [rejected]        master -> master (non-fast-forward)

#需要使用--force
git push
--force origin master

注意⚠️没有同步更新remote-tracking branch!

当本地master分支和origin/master在一起时,执行一次修改并commit,再git push, 这时origin/master不能同步更新,需要再次执行一次同步命名。git push ,git fetch都可以.

 

再次提交一次:

#修改一些文件并提交
git commit -am 'd'

 

可以发现origin/master是处于4ebc769,

如果使用

git fetch

 

origin/master会更新到最新commit点。 

 

删除远程分支或者tag

实际是推送一个空的分支替换远程一个分支,相当于删除远程的这个分支。

//查看本地和远程的分支
git branch --all
//删除本地分支
git branch -D branch_name
//删除远程分支
git push origin origin :branch_name

 

 

 


 

Git remote 

 

如⬆️代码,git remote add origin <url>, 创建了本地和远程仓库origin的链接!

本地./.git/config文件储存了这条记录信息:

[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        ignorecase = true
        precomposeunicode = true
[remote "origin"]
        url = https://github.com/chentianwei411/practice.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master
config (END)

 

git push -u origin master命令,则添加了[branch "master"]这个记录。

 

所有git remote的操作,都会记录在./.git/config文件中!

//执行git remote remove origin命令:
//会断开本地和远程的连接:
//所有对跟踪远程的分支的设置和对远程的配置设置被移除!

//结果: [core] repositoryformatversion
= 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = true [branch "master"] config (END)

 

再次使用git remote add origin <url>, 即可再连接上远程仓库:见./.git/config文件增加的代码:

[branch "master"]
[remote "origin"]
        url = https://github.com/chentianwei411/practice
        fetch = +refs/heads/*:refs/remotes/origin/*

 

 

git remote get-url --all origin

列出所有的远程连接URLs.

 

git remote show <name>

这条命令会给出详细的关于一个远程连接的配置信息.

先git remote -v命令,查看。然后用git remtoe show <name>:

  • show origin, 
  • show upstream ,
  • shou other_users_repo
* remote origin
  Fetch URL: https://github.com/chentianwei411/practice
  Push  URL: https://github.com/chentianwei411/practice
  HEAD branch: master
  Remote branch:
    master tracked
  Local ref configured for 'git push':
    master pushes to master (up to date)

 

 

git remote prune [--dry-run] origin

Deletes all stale remote-tracking branches under <name>.

删除所有过期的远程跟踪分支, 即origin/xxx

⚠️origin/xxx是存在于本地仓库的,用于接收从远程仓库pull下来的数据。然后再merge到xxx分支。

These stale branches have already been removed from the remote repository
referenced by <name>, but are still locally available in "remotes/<name>".

这些过期的分支已经从远程仓库origin移除了,但是在本地仓库仍然可以在remotes/origin中找到:

⭠ master ⮀ git branch --all
  hotfix
* master
  try
  remotes/origin/hotfix
  remotes/origin/master

 

--dry-run, 列出什么分支会被剪除掉prune!

 git remote prune --dry-run origin
Pruning origin
URL: https://github.com/chentianwei411/practice
 * [would prune] origin/hotfix

 

git remote prune origin
Pruning origin
URL: https://github.com/chentianwei411/practice
 * [pruned] origin/hotfix

 

再次使用git branch -a命令查询所有分支,会发现remotes/origin/hotfix分支已经被删除!! 

 

显示你的remotes

git remote

git remote -v

//-v选项,是verbose的意思
// 会列出标记的仓库名字和相关信息,合作的仓库URL.

origin  git@bitbucket.com:origin_user/reponame.git (fetch)
origin  git@bitbucket.com:origin_user/reponame.git (push)
upstream    https://bitbucket.com/upstream_user/reponame.git (fetch)
upstream    https://bitbucket.com/upstream_user/reponame.git (push)
other_users_repo    https://bitbucket.com/other_users_repo/reponame (fetch)
other_users_repo    https://bitbucket.com/other_users_repo/reponame (push)

 

 

添加远程仓库

当你添加了一个远程仓库。 你就可以使用仓库名字origin作为<url>的简写,在其他git命令上使用了。

这是因为./.git/config中记录了url的信息:

[remote "origin"]
        url = https://github.com/chentianwei411/practice
        fetch = +refs/heads/*:refs/remotes/origin/*

 

 

 

 


 

 

Git Fetch origin <remotebranch-source>

 

和git push正相反,Git会查找remotebranch的本地远程分支,并下载到本地的远程分支。

这样不会弄坏你的本地同名分支。

 

也可以另外指定远程仓库的分支下载到哪个本地的分支<destination>,

这样会直接下载到某个分支上(不是远程分支),⚠️开发人员很少这么做,有风险。

 

如果本地没有一个bar分支:

git fetch origin XX:bar的结果是, 会在本地自动创建一个bar分支,用来存储远程仓库的commit信息。

 

如果只有git fetch:

Git会下载远程仓库中所有的提交记录到各个远程分支...

 


 

省去<source> 的特殊用法:

 

删除远程仓库的分支:

git push origin :foo  #推送一个空的source到远程仓库,如果远程仓库有foo分支,这个分支将被删除。

 

git fetch origin :bar  #下载一个远程仓库没有的空分支给本地,本地创建一个bar分支。

 


 

 

Git Pull参数

git pull origin foo 相当于:

git fetch origin foo; git merge o/foo

 

⚠️git pull origin master 会先下载到o/master, 然后merge到当前checkout检出位置。

所以,使用git pull要不熟练,要不就别用。

 

git pull也可以使用<source>:<destination>:

如:git pull origin master:foo

  1. 如果本地没有foo, 先在本地创建一个foo分支,
  2. 然后从远程仓库下载master分支中的提交记录并合并到foo分支
  3. 然后再merge到当前的检出分支checkout分支上。

 

 


  

git clean 

 

将未跟踪的文件从你的工作目录working directory中移除。

未跟踪文件是新增到在工作目录但尚未添加到用 git add添加到repo's index。

它只是提供了一条捷径,因为用 git status 查看哪些文件还未跟踪然后手动移除它们也很方便。

和一般的 rm 命令一样,git clean是无法撤消的,所以在删除未跟踪的文件之前想清楚,你是否真的要这么做。

 

git clean 命令经常和 git reset --hard 一起使用。

⚠️记住,reset 只影响被跟踪的文件,所以还需要git clean来清理未被跟踪的文件。这个两个命令相结合,你就可以将工作目录回到之前特定提交时的状态。 

# 先测试一下比较好-n ,--dry-run
git clean --dry-run

#
移除当前目录下未被跟踪的文件。-f(强制)标记是必需的。
# 不会删除未跟踪的目录directory和.gitignore中的文件。
git clean -f # 移除未跟踪的文件,但限制在某个路径下 git clean -f <path> # 测试移除未跟踪的目录directory git clean -dn

# 移除未跟踪的目录

git clean -d

 

⚠️请牢记,和 git reset --hard 一样, git clean 是仅有的几个可以永久删除提交的命令之一,所以要小心使用 

栗子:

# 编辑了一些文件
# 新增了一些文件
# 『糟糕』

# 将跟踪的文件回滚回去, 新增文件从head, index, 工作目录中删除了!!修改的文件恢复到之前的代码!!
git reset --hard

# 移除未跟踪的文件,先测试-n, -dn
git clean -n git clean -df

 


 

改变旧的或者多个commits: git rebase 

 

rebase改基。从一个分支移动到另一个分支,即改基。

移动或联合一系列的提交点到一个新的base commit。实际是创建了一系列新的提交点。

 

效果和目的:

⚠️不要用在public commit。

  1. 让你修改你的历史, 并且可交互的改基允许你如此做而不留下杂乱的痕迹。
  2. 让你在修改错误和从新定义你的任务后,仍然保持了一个干净,线性linear的程序历史。

在真实的场景:

  1. 在主分支发现bug.。一个功能分支的功能由此坏掉。
  2. 开发者检查主分支历史git log。 因为clean history,开发者能快速的找出project的历史。
  3. 开发者使用git log还是不能识别出bug是何时插入的introduced。所以他执行了git  bisect
  4. 因为git history很干净, git bisect有一个refined set of commits 来比较。开发者快速的找到了这个插入bug的commit点。

 

例子:

当你在一个功能分支上进行开发时,主分支发现一个bug。

新增一个hotfix分支,用于fix bug。 在搞定bug后, 把bug分支合并到master。

你想要在你的功能分支上使用最新版本的主分支,但你想要保持你的功能分支的历史干净,即好似你一直在最新版的master上开发功能分支。

# 开始新的功能分支
git checkout -b new-feature master
# 编辑文件
git commit -a -m "Start developing a feature"


#在 feature 分支开发了一半的时候,我们意识到项目中有一个安全漏洞:----

# 基于master分支创建一个快速修复分支
git checkout -b hotfix master
# 编辑文件
git commit -a -m "Fix security hole"
# 合并回master
git checkout master
git merge hotfix
git branch -d hotfix

#将 hotfix 分支并回之后 master,我们有了一个分叉的项目历史。----
git checkout new-feature
git rebase master

#它将 new-feature 分支移到了 master 分支的末端,
#然后在master上进行标准的快速向前合并了:
git checkout master
git merge new-feature

 

Rebasing的一个常用方式是:把upstream的变化集成到你的本地仓库。

 

 移动整个功能分支⬆️

 

git rebase -i <base>

用 -i 标记运行 git rebase 开始交互式 rebase。交互式 rebase 给你在过程中修改单个提交的机会,而不是盲目地将所有提交都移到新的基上。你可以移除、分割提交,更改提交的顺序。

 

讨论

交互式 rebase 给你了控制项目历史的完全掌控。它给了开发人员很大的自由,因为他们可以提交一个「混乱」的历史而只需专注于写代码,然后回去恢复干净。

大多数开发者喜欢在并入主代码库之前用交互式 rebase 来完善他们的 feature 分支。他们可以将不重要的提交合在一起,删除不需要的,确保所有东西在提交到「正式」的项目历史前都是整齐的。对其他人来说,这个功能的开发看上去是由一系列精心安排的提交组成的。

 

执行:git rebase -i master 后出现vim的交互界面:

  • 如果删除所有pick,然后:wq保存退出,相当于取消rebase 这条命令。提示:Nothing to do
  • 如果直接:q退出,不做任何修改,相当于执行了git rebase master命令。
    • 提示:Successfully rebased and updated

 

如果只pick 37c8c61,第一行。则提示成功:

Successfully rebased and updated refs/heads/b2.

 
然后就可以快速前进的合并了:
git checkout master
git merger b2

 

⚠️另外2个pick就被分支b2扔掉了!!

如果知道这2个commit的id,就可以进入它们:

git chekcout xxxx

得到提示信息You are in 'detached HEAD' state. 你处于分离的HEAD!

可以把这2个提交点合并到b2, 再把b2合并到master。

 

Rebasing的其他几个有趣的命令:

  • edit
  • reword -可以重写提交信息message
  • squash -把当前的commit合并到上一个commit, 并提示你重写提交message
  • fixup    -和squash一样,不会重写提交message.

 

edit 9a29b81 

如可以使用命令edit, 代替pick,当:wq保存退出vim后,提示

⭠ b3 ⮀ git rebase -i master
Stopped at bb28411... change yangcheng
You can amend the commit now, with

git commit
--amend
Once you are satisfied with your changes, run
git rebase
--continue

➦ bb28411 ⮀

 

然后你可以在这个提交点上做修改,然后

➦ bb28411± ⮀ git add .
➦ bb28411± ⮀ git commit --amend
[detached HEAD 36f12d6] change yangcheng
 Date: Sun Nov 25 11:45:44 2018 +0800
 1 file changed, 1 insertion(+), 1 deletion(-)
➦ 36f12d6 ⮀ git rebase --continue
Successfully rebased and updated refs/heads/b3.
⭠ b3 ⮀

 

squash 9a29b81

合并后放入新增的一个commit中。 这是rebase的主要功能!!

原因:

大量的细小的改动,每个改动都是一个提交。导致仓库的history看起来很乱。

许多这样的commit并没有实际地给你的仓库history增加任何价值

它们弄乱了blame(新旧版本的对比), make bisects take longer and make the history hard to navigate.

⭠ b4 ⮀ git rebase -i master

#此时进入vim编辑器。重写提交message,然后:wq
[detached HEAD fac0e93] change content
and guangzhou Date: Sun Nov 25 12:42:45 2018 +0800 2 files changed, 3 insertions(+), 1 deletion(-) Successfully rebased and updated refs/heads/b4.

 

然后:

git checkout master
git merge b4
git branch -d b4

 

还有另一种使用squash的方式:

git merge --squash <commit> 可以把一个分支合并为一个commit, 然后执行

git commit -m 'squash ...' 

 

最后删除分支b3:  git branch -D b3


 

安全网:git reflog  

Git保持对分支的tip的追踪!这种机制叫做reflog(reference logs), 引用日志。

Git 用引用日志这种机制来记录分支顶端的更新和其他commit引用。

它允许你回到那些不被任何分支或标签引用的commits。在重写历史后,reflog包含了分支旧状态的信息,有需要的话你可以回到这个状态。

每次当你的分支tip被任何原因所更新(包括切换分支,pulling in new changes, 重写历史或者仅仅是增加新的commits), 一个新的entry将被增加到reflog。

reflog提供了一张安全网,所有的分支顶端的变化都会被记录。

另外, reflog提供了到期日。默认设置expiration time是90天。

 

用法

git reflog
# 是git reflog show HEAD的简写

显示: 

fac0e93 HEAD@{0}: merge b4: Fast-forward
6f70a12 HEAD@{1}: checkout: moving from b4 to master
fac0e93 HEAD@{2}: rebase -i (finish): returning to refs/heads/b4
fac0e93 HEAD@{3}: rebase -i (squash): change content and guangzhou
0481ff2 HEAD@{4}: rebase -i (pick): change content
6f70a12 HEAD@{5}: rebase -i (start): checkout master
3bc3338 HEAD@{6}: checkout: moving from master to b4
。。。
#fac0e96,第一行是最新的一次relog。

 

还可以用:

git reflog --relative-date

fac0e93 HEAD@{3 hours ago}: merge b4: Fast-forward
6f70a12 HEAD@{3 hours ago}: checkout: moving from b4 to master
fac0e93 HEAD@{4 hours ago}: rebase -i (finish): returning to refs/heads/b4
...

#显示相对现在,每条记录发生的时间!

 

默认,git reflog会输出HEAD ref。但也可以显示其他ref。

如其他分支,tags, remotes, Git stash都可以被引用。

引用中的语法格式:name@{qualifier}

获得全部的reflog:

 git reflog show --all

 

查看具体某一个分支的引用:

⭠ b5 ⮀ git reflog show b5
//显示:
905b28c b5@{0}: commit: change bj
fac0e93 b5@{1}: branch: Created from HEAD
(END)

 

如果使用过git stash命令储存了缓存区的文件, 并且还未取出,则可以用git reflog命令查看记录:

git reflog stash
//显示
bd1f5f7 stash@{0}: WIP on b5: 905b28c change bj

 

 

另外, 可以使用git diff: Show changes between commits, commit and working tree

//git diff stash@{0} otherbranch@{0}
⭠ b5 ⮀ git diff stash@{0} b5@{0}
//显示 diff --git a/beijing.txt b/beijing.txt index 7719339..6815264 100644 --- a/beijing.txt +++ b/beijing.txt @@ -1,3 +1,4 @@ +123 hahaha hello hello this is a beautiful city! (END)

 

一般用不到,使用图形编辑器sourcetree,就可直观的看一个commit的变化。

但用代码,可以有更丰富的细节设置,如加上一个到期的时间:

 git diff master@{0} master@{1.day.ago}

 

具体可见git diff --help

 

恢复丢失的commits

Git 从不真地丢失任何东西,继续执行了历史重写操作,如rebasing, commit amending。

 

git log的搜索功能很强大,有丰富的设置可以查看各种情况。

 

例如:

分支b5有4个commit,master有1个commit超过b5

⭠ b5 ⮀ git rebase -i master
//进入vi编辑器。把第4行的pick改成squash, :wq。
//terminal:
[detached HEAD 55ce6f9] change one
 Date: Sun Nov 25 17:06:31 2018 +0800
 2 files changed, 3 deletions(-)
Successfully rebased and updated refs/heads/b5. 

 结果附加到master上的commit只有3个,最后一个是合并的commit

git log --pretty=oneline
//也只能看到新增的3行log
55ce6f9c6f60d80f9b042f5f8a99556333d5854a change one
23bd2a5ead2b5a19d6bcc8667fbd23c636135264 change sth
c4e32fb82669aa16ac60e2b9f8a1bc488a366be5 change bj

 

似乎b5分支的最后2个commit,由于squash,导致无法找到了!其实不然:

使用git reflog命令,即可看到最新的ref日志⬇️:

55ce6f9 HEAD@{0}: rebase -i (finish): returning to refs/heads/b5
55ce6f9 HEAD@{1}: rebase -i (squash): change one
07e7fbf HEAD@{2}: rebase -i (pick): 1
23bd2a5 HEAD@{3}: rebase -i (pick): change sth
c4e32fb HEAD@{4}: rebase -i (pick): change bj
eb80dcf HEAD@{5}: rebase -i (start): checkout master
1c226ea HEAD@{6}: checkout: moving from master to b5

可以看到从start到finish的全部细节:一部了然!! 

  • 4行绿色是4个commit点
  • 第2行操作的方式是squash

如果想要恢复到执行git rebase之前,可以使用:

git reset HEAD@{6}



 

 

保存改变的5个命令:

  • git add

  • git commit

  • git diff (比较commit的不同,可以用可视化工具:sourcetree, 或者上远程仓库,在网页上看。)

  • git stash(上面已经介绍,把staged和未staged的文件储存起来)

  • .gitignore :这里就介绍它。

 

.gitignore 

Git从copy的行为上分类: 把文件分成3种类别:

  1. tracked -一个文件之前被staged or commited
  2. untracked -一个从未被staged or commited的文件,一般是新建的文件。
  3. ignored -不加入tracked的文件。

包括:

  • 独立缓存/packages
  • build output directories:如 /bin, /out, /target
  • compiled code:  .pyc, .class文件
  • 在运行时产生的文件:  .log, .lock, .tmp
  • 隐藏的系统文件:  .DS_store, Thumbs.db
  • 个人的IDE配置文件

一般可以在~/.gitignore文件内查看仓库中被忽略的文件:

当你有新的文件需要被忽略,.gitignore文件必须手动编辑和提交。

 

Global Git ignore rules

你需要自己建立.gitignore, 并设置core.excludesFile属性。

$ touch ~/.gitignore
$ git config --global core.excludesFile ~/.gitignore

 

 

Shared .gitignore files in your repository

通常,Git ignore rules被定义在仓库根目录的.gitignore文件中。

但也可以在你的仓库中的不同的目录中定义多个.gitignore文件。

然而最简单的方法还是在根目录创建.gitignor文件,因为它本身也是被版本控制的,当你push,就可以和团队共享。

 

Personal Git igonore rules

你也可以定义个人的ignore模式,这在特殊的.git/info/exclude文件中。

它不会被版本控制!所以你可以做一些私事!

 

如何忽略一个之前commit过的文件?

从仓库删除这个文件,然后增加一个.gitignore rule.

使用 --cached选项和git rm

git-rm - Remove files from the working tree and from the index.

--cached: unstage and remove paths only from the index. 但会保留在working directory!

例子:

$ echo debug.log >> .gitignore
$ git rm --cached debug.log
//提示:rm 'debug.log'   
$ git commit -m "Start ignoring debug.log"

[master 6d51c73] Start ignoring debug.log
1 file changed, 1 deletion(-)
delete mode 100644 debug.log


.gitignore自身是被追踪的!,需要git add .gitignore

 

 

Committing an ignored file

可以强制一个被忽略的文件被提交到仓库,使用-f选项即可:

$ cat .gitignore
*.log
$ git add -f debug.log
$ git commit -m "Force adding debug.log"

 

当然,这不是一个明显的,可以让团队成员了解的方法:改为:

$ echo !debug.log >> .gitignore
$ cat .gitignore
  *.log
  !debug.log
$ git add debug.log
$ git commit -m "Adding debug.log"

 

*.log表示所有带.log后缀的文件都会被忽略,但是!dubug.log表示这个文件不会被忽略!

 

Stashing an ignored file

使用git stash回你历史的存储本地的变化,并在之后用git stash pop取回。

但是默认git stash忽略.gitignore中的文件。

使用--all选项,可以stash 忽略的和未跟踪的文件。

具体git stash 说明文档见:https://www.atlassian.com/git/tutorials/git-stash/#stashing-untracked-or-ignored

 

Debugging .gitignore files

如果你有复杂的.gitignore模式或者有多个.gitignore文件,那么想要知道一个文件为何被忽略,就比较麻烦。

你可以使用git check-ignore -v <file-name>来看什么模式让这个文件被忽略

 


 

 

git rm

用于把一个文件从a Git repository中移除(working directory和index, 或者只从index中移除)。

某种程度上可以认为是git add命令的相反命令。

 

git rm命令可以用于移除单独的文件或一组文件集合。

它的主要功能是从Git index中移除跟踪的文件。

另外它也能同时从staging index和 working directory移除文件。

注意git rm 不会移除branches。

 

选项--cached: 

这个选项的用途是取消文件的tracking,但保留这个文件在working directory!

案例见上.gitignore

 

如何undo git rm?

git rm命令需要执行git commit来生效。

git rm将更新staging index 和 working directory,

⚠️这些变化不会马上产生效果,只有当新的commit被创建,这些变化才会被添加到commit history中。

这就意味着git rm可以恢复。(符合git 作为时光机的原则)执行git reset HEAD

 

⚠️git rm只能用在当前current branch的文件!

 

为什么使用git rm来代替rm

当一个被跟踪的文件被rm命令执行,Git仓库会识别这个标准的壳命令rm。

Git仓库会更新working directory来反应这个移除。但它不会更新staging index。

所以需要额外的git add命令,把这个移除的改变添加到staging index。

git rm 是一个方便的shorcut。它将同时更新working directory and the staging index 。

例子:取消文件shanghai.txt的跟踪,并删除这个文件。

⭠ master ⮀ git rm shanghai.txt
提示:rm 'shanghai.txt'
⭠ master± ⮀ git status 提示: On branch master Your branch is ahead of 'origin/master' by 4 commits. (use "git push" to publish your local commits) Changes to be committed: (use "git reset HEAD <file>..." to unstage) deleted: shanghai.txt   #帮你使用了git add命令。

 

⚠️sourcetree,可以使用discard,取消一个文件的修改或删除。就是恢复操作!

等同命令:

git checkout <file>
git checkout -- <file> #加上-- 防止文件名和branch名字重复导致❌。

 

 

Unstage a file:

git reset HEAD <file>  
git rm --cached <file> #删除staging保留working direotry中的文件。

 

 

小节:

git rm --cache <file>用于不再对某个文件进行tracking。之前commi过的文件,不再跟踪!

git clean -df 删除未跟踪的文件和目录

git reset --hard 回滚跟踪的文件,即恢复文件到未修改的状态并不再跟踪,新增的文件则从工作目录删除。 

 



 

 

git fetch 命令是如何与remote branches 合作的?(深入分析)

Behind the scenes,在幕后, ./.git/objects目录中,Git存储了所有的commits, 包括local和remote。

~/practice/.git/objects ⮀ ⭠ master ⮀ ls
00   0d   1b   24   38   40   51   60   70   7e   8b   9b   a8   bb   c7   d4   e7   fd
02   11   1c   27   3b   43   55   65   76   81   90   9d   ab   bd   c8   de   eb   info
04   15   1f   2d   3c   49   59   68   77   84   92   a1   ad   be   ce   e0   f0   pack

 

通过使用branch refs(就是commit点的🆔), GIT让分支commits清楚的分开。

远程分支的Refs,  储存在./.git/refs/remotes/

//👇两条命令都可以查看
git branch -r
git branch --all

 

本地分支的Refs,储存在./.git/refs/heads/

~/practice/.git/refs/heads ⮀ ⭠ master ⮀ ls
hotfix master try

 less try可以看到:

ad64e76234f1dee8ea19618d4f5964edbc20bd36
try (END)

 


 

git log

针对commit点的查询和搜索功能:

//在屏幕上显示几行
git log -n <limit>

//单行显示,用于全局的概览, 内容包括id和messages
git log --oneline

// 显示每个commit的文件改变的概览 git log
--stat //使用-p选项,看每个提交点的patch。比--stat更详细的每个commit的区别 git log -p

  

通过对message进行匹配来搜索,<pattern>可以是字符串和正则表达式:

git log --grep="<pattern>"

  

看一个特定文件的提交历史:

git log <file>

  

显示一个类似sourcetree的结构:

git log --graph --decorate --oneline

 

⚠️,选项可以一起使用。

 

 

git blame

  • The high-level function of git blame is the display of author metadata attached to specific committed lines in a file. This is used to explore the history of specific code and answer questions about what, how, and why the code was added to a repository.

用于比较commit点的历史代码。信息还包括添加代码的作者名字,解释。

合作者可以查看这些历史代码。

一般使用UGI网页,如在线git上查看这些历史代码。

例子:example

git clone https://kevzettler@bitbucket.org/kevzettler/git-blame-example.git 
cd git-blame-example

//使用git log查看所有commit信息。
git log

 

 git blame只用于单独的文件的历史commit点的比较。需要提供file-path来输出信息。

//输出帮助信息
git blame

//输出文件的commit历史: 即每个commit的代码的增减。
git blame README.md

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2018-07-06 19:31  Mr-chen  阅读(768)  评论(0编辑  收藏  举报