Git 学习

config

  • 设置用户名和账号 使用了--global参数 表示你这台机器上的所有仓库都会用这个配置

    1
    2
    git config --global user.name "Your Name"
    git config --global user.email "email@example.com"
  • 查看配置

    1
    git config --list

创建仓库

  • 创建文件夹

    1
    2
    mkdir learngit
    cd learngit
  • 初始化仓库 初始化后会生成一个.git目录,这个目录是隐藏的,不要手动删掉

    1
    git init
  • 把文件添加到仓库

    1
    2
    3
    git add yourfile.txt 
    //或者添加所有
    git add .
  • 提交到仓库 -m后面输入的是本次提交的说明,输入有意义的说明很重要

    1
    git commit -m "wrote a yourfile.txt"
    • 注意-am区别

      1
      git commit -am "xxxxx"

      可以省略使用git add命令将已跟踪文件放到暂存区的功能,如果是新增的文件,处于未跟踪状态无法提交

    • 为什么git需要addcommit两步

      1
      2
      3
      git add file1.txt
      git add file2.txt file3.txt
      git commit -m "add 3 files"

      因为commit可以一次提交很多文件,多次add不同的文件

仓库操作

git status 查看状态

  • 下面表示ztt.txt这个文件被修改过

git diff 比较修改

  • 顾名思义就是查看文件的对比,我们可以看出第一行增加了add content

  • 查看完修改的内容就可以放心提交了git commit -am "add content"

git log 查看快照

  • 查看快照 即每次commit后的内容,从下面可以看出我们做过两次提交,显示的顺序是从近到远

git reset 撤销

撤销已经提交到分支的恢复

  • 让我们再修改下文件 增加一行add footer,再查看下git log

  • 方法一 恢复到上一个版本,~x代表回退到上几个版本

    1
    git reset --hard HEAD~1

  • 方法二 恢复到未来版本 ,只要窗口没关闭的话你可以,往上找到版本号,不用写全,会自动补全(当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。)

    1
    git reset --hard 版本号xxx

撤销已经提交到暂存区的操作

  • 第一步执行 文件会恢复到修改后的工作区 未添加到暂存区状态

    1
    git reset HEAD yourfile.txt

    HEAD表示最新的版本

  • 第二步 恢复到修改前的文件

    git checkout -- yourfile.txt

git reflog 查看操作过程

  • 用于查找commit id,它会记录你的每一次操作

工作区和暂存区

  • 工作区:我们存放文件的文件夹,.git这个隐藏目录是git的版本库

  • git版本库中最重要的称为Stage即暂存区,git会为我们自动创建第一个分支master,以及指向master的一个指针HEAD,我们控制版本就是移动这个指针来更新文件

    第一步git add就是把文件添加到暂存区

    第二步git commit就是把暂存区的内容提交到当前分支

git checkout – yourfile.txt 恢复文件

  • 情况一 文件自修改后还没被放到暂存区,撤销修改就回到修改前的状态

  • 情况二 文件已经添加到暂存区,又做了修改,撤销修改就回到添加到暂存区后的状态

    注意命令中的--很重要,如果没有就变成了切换到另一分支的命令

git rm 删除暂存区的文件

  • 对应git add的操作

  • 现在工作区新建一个test.txt文件,并提交到分支,再在工作区手动删除文件,查看状态

    • 情况一 删除暂存区的test.txt文件

    • 情况二 不小心误删的,因为暂存区还有保存着一份,故可以执行以上的命令恢复文件

远程仓库

GitHub

  • 本地Git仓库和GitHub仓库之间的传输是通过SSH加密的

    • 创建SSH Key,在用户主目录下(cmd),查看是否存在.ssh目录,若没有创建

      1
      ssh-keygen -t rsa -C "youremail@example.com"

    • 之后就可以找到.ssh目录,其中id_rsaid_rsa.pub两个文件,这两个就是SSH Key的秘钥对,id_rsa是私钥,不能泄露,id_rsa.pub是公钥,可以放心给别人

    • 登录Github,打开setting,选择SSH and GPG keys ,创建New SSH key

​        Title任意填,Key粘贴公钥里的内容即可`id_rsa.pub `

添加远程库

  • github创建一个新的Repository

  • 然后可以根据github提示来

    • 在本地仓库运行命令 注意地址是你自己github的

      1
      git remote add origin https://github.com/yourgithub/test.git

      添加后远程库的名字就是origin,这是Git默认的叫法,可以换成别的,但是不建议,因为origin本意就是远程库

    • 推送本地内容到远程库

      1
      git push -u origin master

      git push命令,实际上是把当前分支master推送到远程

      由于第一次推送,远程库是空的,所以加上了-u,Git会把本地的master分支内容推送到远程新的master分支,还会把本地master分支和远程mster分支关联起来,方便以后推送和拉取可以简化命令

    • 之后只要本地做了提交,可以通过以下命令把本地master分支最新修改推送到远程库

      1
      git push origin master

git clone xxxx 克隆远程库到本地

  • 地址可以是浏览器上的https://xxxx
  • 也可以是SSH的 git@github.com:xxxx

分支管理

  • 使用场景即多人协作,当你代码还没完成需要提交避免进度丢失,但是提交又会影响其他人开发时,创建自己的分支,这样就不会相互影响,等开发完毕再一次性合并到原来的分支上

创建与合并分支

原理

  • master分支是一条线,HEAD指向master,每次提交master分支都会向前移动一步,这样随着你不断提交master分支线会越来越长

    • 创建dev分支,指向master相同的提交,HEAD指向dev,从此对工作区的修改和提交都是针对dev分支,比如新提交,dev指针会往前移动一步,但是master不会移动

  • 合并devmaster,直接把master指向当前dev提交

  • 合并完成之后就可以把dev分支删除,只剩下master分支

实例

  • 创建并切换分支到新创建的dev分支

    1
    git checkout -b dev

    上面的相当于执行两句

    1
    2
    git branch dev
    git checkout dev
  • 查看当前分支

    1
    git branch

  • 增加new.txt文件,提交到dev分支,切换到master分支看不到这个文件,这是因为这个文件提交到的是dev分支,现在切换到master分支做合并分支操作,之后可以删除dev分支

    1
    2
    git checkout master
    git merge dev

解决冲突

  • 新增并切换到分支demo

    1
    git switch -c demo

    由于git checkout 担任角色太多 既要担任切换分支又要担任恢复文件,故用以上代替 git checkout -b demo

  • 文件new.txt中增加一行A & B,然后提交到分支

  • 切换到master分支,再次修改new.txt文件,增加一行A and B,然后提交分支

  • 执行git merge demo合并分支命令,git status会有冲突提示

    其中提示比远程仓库提前了2个提交Your branch is ahead of ‘origin/master’

    git merge demo 是采用Fast forward模式,禁用该模式可以加参数 git merge –no-ff -m “xxx”,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而fast forward合并就看不出来曾经做过合并。

  • 查看new.txt文件,Git用<<<<<<<=======>>>>>>>标记出不同分支的内容,

  • 修改new.txt文件,解决冲突后提交即可,删除demo分支

    1
    git branch -d demo

    git log –graph –pretty=oneline –abbrev-commit 可以查看分支合并图

保存工作区

  • 应用场景修复bug,但是当前你是处在分支demo上处理问题,问题只处理到一半不能提交,但是领导要求你要先修复bug,解决方法如下

    • 保存分支demo工作区内容,你可以多次保存

      1
      git stash

    • 切换到master,创建并切换到新分支例如issue-101,修复bug,切换回master直接合并分支

    • 切换回demo工作区继续干活,首先就要恢复工作区

      1
      2
      3
      4
      5
      6
      git stash list //查看

      git stash apply stash@{0} //恢复指定的位置
      git stash drop //删除

      git stash pop //上面两句可以用这句代替

    • 提交demo分支的工作

    • 复制master刚刚修复的bug

      • 这是提交的master的bug

      • 复制master的bug

        1
        git cherry-pick <commit>

git branch -D 强行删除一个还没有合并的分支

多人协作

  • 查看远程库信息

    1
    2
    git remote
    git remote -v //更详细信息
  • 推送分支

    1
    git push origin master //远程库名字  分支也可以是dev

    并不是一定要把本地分支远程推送

    • master分支是主分支,因此要时刻与远程同步
    • dev分支是开发分支,团队所有成员需要在上面工作,所以也需要与远程同步
    • bug分支是用于本地修复bug,就没必要推送到远程了
    • feature分支是否推送远程,取决于你是否和你的小伙伴合作在上面开发

git rebase

  • 多人在同一个分支上协作时,很容易出现冲突。即使没有冲突,后push的童鞋不得不先pull,在本地合并,然后才能push成功。

    每次合并再push后,分支变成了这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    $ git log --graph --pretty=oneline --abbrev-commit
    * d1be385 (HEAD -> master, origin/master) init hello
    * e5e69f1 Merge branch 'dev'
    |\
    | * 57c53ab (origin/dev, dev) fix env conflict
    | |\
    | | * 7a5e5dd add env
    | * | 7bd91f1 add new env
    | |/
    * | 12a631b merged bug fix 101
    |\ \
    | * | 4c805e2 fix bug 101
    |/ /
    * | e1e9c68 merge with no-ff
    |\ \
    | |/
    | * f52c633 add merge
    |/
    * cf810e4 conflict fixed
  • 上面看上去很乱 ,从例子看,hello.py这个文件做了两次提交。用git log命令看看

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    $ git log --graph --pretty=oneline --abbrev-commit
    * 582d922 (HEAD -> master) add author
    * 8875536 add comment
    * d1be385 (origin/master) init hello
    * e5e69f1 Merge branch 'dev'
    |\
    | * 57c53ab (origin/dev, dev) fix env conflict
    | |\
    | | * 7a5e5dd add env
    | * | 7bd91f1 add new env
    ...

    注意到Git用(HEAD -> master)(origin/master)标识出当前分支的HEAD和远程origin的位置分别是582d922 add authord1be385 init hello,本地分支比远程分支快两个提交。

  • 推送本地分支

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ git push origin master
    To github.com:michaelliao/learngit.git
    ! [rejected] master -> master (fetch first)
    error: failed to push some refs to 'git@github.com:michaelliao/learngit.git'
    hint: Updates were rejected because the remote contains work that you do
    hint: not have locally. This is usually caused by another repository pushing
    hint: to the same ref. You may want to first integrate the remote changes
    hint: (e.g., 'git pull ...') before pushing again.
    hint: See the 'Note about fast-forwards' in 'git push --help' for details.
  • 这说明有人先于我们推送了远程分支。按照经验,先pull一下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ git pull
    remote: Counting objects: 3, done.
    remote: Compressing objects: 100% (1/1), done.
    remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0
    Unpacking objects: 100% (3/3), done.
    From github.com:michaelliao/learngit
    d1be385..f005ed4 master -> origin/master
    * [new tag] v1.0 -> v1.0
    Auto-merging hello.py
    Merge made by the 'recursive' strategy.
    hello.py | 1 +
    1 file changed, 1 insertion(+)
  • 查看状态

    1
    2
    3
    4
    5
    6
    $ git status
    On branch master
    Your branch is ahead of 'origin/master' by 3 commits.
    (use "git push" to publish your local commits)

    nothing to commit, working tree clean
  • 加上刚才合并的提交,现在我们本地分支比远程分支超前3个提交。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ git log --graph --pretty=oneline --abbrev-commit
    * e0ea545 (HEAD -> master) Merge branch 'master' of github.com:michaelliao/learngit
    |\
    | * f005ed4 (origin/master) set exit=1
    * | 582d922 add author
    * | 8875536 add comment
    |/
    * d1be385 init hello
    ...
  • 上面看起来很乱,我们使用命令整理成直线

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ git rebase
    First, rewinding head to replay your work on top of it...
    Applying: add comment
    Using index info to reconstruct a base tree...
    M hello.py
    Falling back to patching base and 3-way merge...
    Auto-merging hello.py
    Applying: add author
    Using index info to reconstruct a base tree...
    M hello.py
    Falling back to patching base and 3-way merge...
    Auto-merging hello.py

    再查看

    1
    2
    3
    4
    5
    6
    $ git log --graph --pretty=oneline --abbrev-commit
    * 7e61ed4 (HEAD -> master) add author
    * 3611cfe add comment
    * f005ed4 (origin/master) set exit=1
    * d1be385 init hello
    ...

    其实原理非常简单。我们注意观察,发现Git把我们本地的提交“挪动”了位置,放到了f005ed4 (origin/master) set exit=1之后,这样,整个提交历史就成了一条直线。rebase操作前后,最终的提交内容是一致的,但是,我们本地的commit修改内容已经变化了,它们的修改不再基于d1be385 init hello,而是基于f005ed4 (origin/master) set exit=1,但最后的提交7e61ed4内容是一致的。

    rebase操作的特点:把分叉的提交历史“整理”成一条直线,看上去更直观。缺点是本地的分叉提交已经被修改过了。

    最后,通过push操作把本地分支推送到远程:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Mac:~/learngit michael$ git push origin master
    Counting objects: 6, done.
    Delta compression using up to 4 threads.
    Compressing objects: 100% (5/5), done.
    Writing objects: 100% (6/6), 576 bytes | 576.00 KiB/s, done.
    Total 6 (delta 2), reused 0 (delta 0)
    remote: Resolving deltas: 100% (2/2), completed with 1 local object.
    To github.com:michaelliao/learngit.git
    f005ed4..7e61ed4 master -> master

    再用git log看看效果:

    1
    2
    3
    4
    5
    6
    $ git log --graph --pretty=oneline --abbrev-commit
    * 7e61ed4 (HEAD -> master, origin/master) add author
    * 3611cfe add comment
    * f005ed4 set exit=1
    * d1be385 init hello
    ...
  • 小结

    • rebase操作可以把本地未push的分叉提交历史整理成直线;
    • rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比。

标签

创建标签

  • 切换到需要打标签的分支上,然后执行命令

    1
    git tag v0.1
  • 查看所有标签

    1
    git tag
  • 使用git log查看,找到历史提交的commit id 打标签

    1
    git tag v0.1 f52dasf
  • 查看标签信息

    1
    git show v0.1
  • 创建带有说明的标签

    1
    git tag -a v0.1 -m "version 0.1" 1324324

    -a 表示标签名 -m 表示说明

  • 注意:标签总是和某个commit挂钩。如果这个commit既出现在master分支,又出现在dev分支,那么在这两个分支上都可以看到这个标签。

操作标签

  • 删除标签

    1
    git tag -d v0.1
  • 推送标签到远程仓库

    1
    git push origin v1.0

    或者一次性推送所有标签

    1
    git push origin --tags
  • 如果标签已经推送到远程,删除需要先删除本地,再删除远程

    1
    2
    git tag -d v0.1
    git push origin :refs/tags/v0.1

使用Github

  • 在GitHub上,可以任意Fork开源仓库;

  • 自己拥有Fork后的仓库的读写权限;

  • 可以推送pull request给官方仓库来贡献代码