0%

git学习笔记

使用git很久了,期间踩了一些坑,通常来说对git理解和使用不熟的话踩得坑都是致命的(哭死),所以花了点时间总结了一些用法,加深对git的一些理解

远程主机remote

相当于远程仓库的别名(如ip与域名),git上默认主机名为:origin

查看主机

$ git remote
$ git remote show
$ git remote -v
$ git remote show <主机名>

主机操作

$ git remote add <主机名> <网址>
$ git remote rm <主机名>
$ git remote rename <原主机名> <新主机名>

通常情况下只需要一个远程主机,有时候我们需要本地代码同事push到两个仓库(如:OSChina和github),这个时候可以配置主机的Url

添加主机的Url,可以添加多个网址

$ git remote set-url --add <主机名> <网址>

当push到主机的时候会自动同步到添加的网址
拉去的时候会直接则取第一个url,修改可以在config(.git/config)文件进行修改

克隆仓库clone

clone是远程操作的第一步,把远程仓库取回到本地

$ git clone <版本库的网址> <本地目录名>

指定主机名

$ git clone -o <主机名> <版本库的网址> <本地目录名>

拉取更新fetch

当远程仓库有更新的时候,可以通过fetch拉取

$ git fetch <远程主机名> <远程分支名>:<本地分支名>

远程分支名缺省,则拉去与本地分支有track关系远程分支
拉去后不会merge到当前分支,而是放到一个临时分支上

拉取并合并更新pull

$ git pull <远程主机名> <本地分支名>:<远程分支名>

push

推送更新

$ git push <远程主机名> <本地分支名>:<远程分支名>

如果分支不存在,则创建
如果本地分支名为空,表示删除远程分支

删除远程分支

$ git push <远程主机名> --delete <远程分支名>:<本地分支名>

只要没有push的改变都只是在本地,包括branch,commit,reset,都可以撤销和删除

比较pull和fetch

pull相当于fetch+merge

$ git pull origin Development  
# 相当于拉取完直接合并到本地分支上
$ git fetch origin Development:Development
$ git merge origin/Development


$ git fetch
# 命令相当于创建一个临时分支,用于拉取
$ git fetch origin Development:tmp

分支 Branck

# 查看分支(所有)
$ git branch -a

# 查看本地分支
$ git branch

# 查看远程分支
$ git branch -r

# 创建分支(不会自动切换)
$ git branch TestBranck

# 切换分支
$ git checkout TestBranck

# 创建并切换
$ git checkout -b TestBranck

# 推送本地分支到主机
$ git push origin TestBranck

# 删除分支(本地)
$ git branch -d TestBranck

# 删除远程分支(本地)
$ git push origin :TestBranck

提交 Commit

  1. 修改/添加文件 test.txt 此时文件没有被跟踪,状态为 Unchecked Files
    丢弃对文件的添加修改,不带文件则撤销全部工作区的修改(不可恢复,慎用)

    $ git checkout -- text.txt
  2. test.txt 文件add到当前分支的暂存区,文件状态为 Changed to be commited

    $ git add test.txt

    添加文件到暂存区后可以取消暂存(此命令不会影响工作区,如果不带文件则撤销全部)

    $ git reset HEAD test.txt
  3. 继续修改文件 test.txt,此时暂存区的文件和工作区的文件不一样了,此时在工作区的副本文件的状态为 Changes not staged for commit

  4. 提交test.txt文件

    $ git commit 添加了一个test.txt文件

    撤销提交,撤销commit比较麻烦,需要拿到上一次提交的commitId

    $ git reset --hard commit_id

    如果是撤销上一次commit,等价于用下面命令

    $ git reset --hard HEAD^
  5. 只有添加到暂存区的文件才能提交,所以我们只提交了第一次修改,第二次修改没有被提交

暂存区的文件都是只读的,不能修改,只有工作区的文件才能修改

问题

  1. 发现刚刚commit漏提交了一个文件(或者提交说明写错了),需要修改上一次commit

    1. 解决一:此时为push到远程仓库,修正上一个commit,把相关需要补充的文件或修改add到HEAD暂存区(保持commitId不变)

      $ git add test.txt

      执行

      $ git commit --amend

      上面命令会追加暂存区的提交到上一次commit,并重新编辑提交说明

    2. 解决二:撤销上一个commit,然后重新commit(新的commitId)
      撤销上一次提交

      $ git reset --hard HEAD^

      回复到指定的commitid

      $ git reset --hard commit_id

      上面操作不会影响工作区

  2. 如果commit已经push到远程
    有时候我们在本地修改后并且push到远程,这个时候发现,需要撤销,如果我们使用git reset撤销后push到远程主机

    $ git reset --HARD HEAD^      # 撤销上一次commit
    $ git push origin master # 这时候push会报错,因为本地的分支比远程的分支落后

    上面push会报错,有两种方式解决

    1. 使用fast-forward方式推送,直接覆盖远程分支,这种方式有风险,有可能在这之前有其他人的push都会一并被覆盖,慎用

      $ git push -f origin master
    2. reset换成revert命令,这种方式会新增一个commit,而不是回退,这个时候当前的分支就不会比远程分支落后了,两者区别见后面

      $ git revert head^                                        # 撤销上一次commit,恢复到AAAA,并生成新的commitId

      # 这个时候可能会产生冲突,解决冲突,commit
      $ git push origin master

      推荐使用这种方式解决远程恢复的问题

文件操作(添加,修改,删除,重命名)

本地对文件的所以修改,都存放在工作区,需要下面命令将修改放到暂存区,才能提交

$ git add test.txt  
$ git rm test.txt
$ git mv test.txt

通常这些操作都借助GUI工具完成

撤销提交 reset/revert

保留原来的commit,会退到历史的点,创建新commit并commit

$ git reset --hard

重置后的修改会被放到暂存区,需要自己commit

$ git reset --soft

mixed为默认参数,重置后的所有修改会被放到工作区

$ git reset --mixed

注意

$ git reset --hard 会清空工作区
$ git reset --soft 不会清空工作区

在使用之前,尽量保证工作区和暂存区没有文件,避免多重冲突

历史是不可以修改的Reflog

git跟踪过的所有的操作都会成为历史,所有的操作都是添加,所以远程的所有操作理论上都是可以恢复的
git能跟踪所有commit, checkout, reset命令,所有这些命令都可以恢复,可以通过reflog查看所有这些操作

$ git reflog

恢复到指定的SHA

$ git reset --hard <SHA>

暂存管理Stash

有时候我们对本地修改到一半时,这时候要去拉去远程更新,为了防止冲突,可以把本地的工作现场保存到另一个暂存区,拉去完成后,在恢复当前工作现场。

Stash可以当前工作区和暂存区的所有修改保存起来,暂存区是全局的,不同分支也共享一个Stash,Stash可以存放多个工作现场

保存当前工作现场(保存暂存区的文件,不保存工作区的文件)

$ git stash

保存当前工作现场(保存暂存区和工作区的文件)

$ git stash --include-untracked
#或者
$ git stash -u

注意:暂存工作区文件有风险,当工作区的文件存在冲突的时候,工作区的文件无法恢复,并且无法自动解决冲突,暂存区的文件可以

恢复工作现场

#恢复上一个保存的工作现场
$ git stash pop
#恢复到指定工作现场(并从stash堆中删除)
$ git stash pop stash@{num}
#恢复到某个工作现场(不从stash堆中删除)
$ git stash apply stash@{num}

查看文件状态(暂存区和工作区)

通常我们通过GUI工具查看

$ git status

取消git版本控制

如果一个目录取消git的版本控制,恢复成正常的目录,可以直接把.git目录删了,包含目录下所有文件和该目录本身

$ rm -rf .git

分歧与Fast-Forward

       #1     #2                #3         #4
master 〇 ─── 〇 ─────────────── 〇 ─────── 〇
| ↑ ↑
A ├───────── 〇 ────── ↑
| checkout push ↑
| ↑
B └───────── 〇 ──────────── ↑
checkout push

A和B都从master分支的的#2checkout代码,假设A对文件README.md做了修改,并push到master,B也对文件README.md做了修改,然后push到master,在默认情况下这个时候就会发生冲突,必须先fetch同步远程最新的代码并merge解决冲突后再提交才能push成功,这是为了防止B的修改直接覆盖A的修改

在默认情况下系统为non-fast-forward,即非快进模式,必须以时间顺序提交,也就是B必须基于#3的代码修改才能提交

解决方法:
1、fetch远程代码后merge解决冲突,然后commmit,再push
2、忽略冲突,直接覆盖远程代码,可以开启fast-forward模式强制覆盖远程的操作

$ git push -f

此操作还会覆盖远程的commit记录,尽量少用,fast-forward模式,特别是在多人开发的时候,会覆盖别人的代码,还会覆盖别人的提交,不利于回滚代码

rebase与merge

rebase和merge都有合并代码的功能,二者的区别在于

  • merge合并后会保留两个分支的所有commit
  • rebase合并后会丢弃合并分支的commit

如下两个分支

  • master:A <- B <- C <- D
  • test: A <- B <- C <- E

Merge:合并test到master

合并之后为:A <- B <- C <- D <- E <- M

Rebase:合并test到master

合并之后为:A <- B <- C <- D <- R

使用rebase可以减少一些多余的commit,让分支历史基本在一条直线上,有利有弊,看使用场景选择merge或rebase

使用git pull默认使用merge,可以加参数--rebase让其使用rebase合并

$ git pull --rebase

合并后,当git无法自动解决冲突的时候,查看分支会出现(no branch, rebasing master),表示不再任何分支上工作

ACA80164:Test bomo$ git branch
* (no branch, rebasing master)
master

这个时候手动解决冲突,然后执行add

$ git add .
# 注意,不需要commit
$ git rebase --continue

总结

  • 所有commmit和reset操作都是可以恢复的(reflog/reset/revert)
  • 工作区的文件通常是不可恢复的
  • checkout是撤销工作区修改,不可恢复,慎用checkout
  • pull之前防止冲突,通常把所有文件放到暂存区(为了防止冲突),然后stash,解决冲突,在stash pop
  • 只有放到暂存区的文件的冲突才能被识别,如果是工作去的冲突,会直接被覆盖
  • 从工作区add文件到暂存区不会识别冲突
  • 所有的commit都可以恢复,建议多commit(提高代码粒度)
  • 所有的操作都是本地的,对远程仓库的操作只有同步操作,如果要修改远程仓库,在本地改完push到服务器