Git

views 1723 words

#Git

Ref

安装

brew install git

https://git-scm.com/downloads

Git 和 SVN 的区别

类型 描述
Git 分布式 本地有镜像,无网络时也可以提交到本地镜像,待到有网络时再push到服务器
SVN 集中式 无网络不可以提交, 和 Git 的主要区别是历史版本维护的位置

分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库 ==> 安全性高; 合作方便

Cheetsheet & Best practice

git-cheatsheet-edited

相关名词理解

-w684

  • Workspace / Working Directory: 工作区 (自己电脑里能看到的目录)
  • Index / Stage: 暂存区
  • Repository: 仓库区(或本地仓库 / 版本库) (工作区有一个隐藏目录 .git, 这个不算工作区, 而是 Git 的版本库)
  • Remote: 远程仓库

Git 的版本库里存了很多东西,其中最重要的就是称为 stage(或者叫index)的暂存区,还有 Git 会自动创建的第一个分支 master,以及指向 master 的一个指针叫 HEAD

  • git add把文件添加进去,实际上就是把文件修改添加到暂存区
  • git commit提交更改,实际上就是把暂存区的所有内容提交到当前分支

配置

### 配置所有 Git 仓库的 用户名 和 email 
$ git config --global user.name "Your Name"
$ git config --global user.email "youremail@example.com"

### 配置当前 Git 仓库的 用户名 和 email
$ git config user.name "Your Name"
$ git config user.email "youremail@example.com"

### 查看全局配置的 用户名 和 email 
$ git config --global user.name     查看用户名
$ git config --global user.email     查看邮箱地址

### 查看当前仓库配置的 用户名 和 email 
$ git config user.name     查看用户名
$ git config user.email     查看邮箱地址

# Git 是分布式版本控制系统,所以每个机器都必须自报家门:你的名字和Email地址
# git config 命令的 --global 参数,用了这个参数,表示你这台机器上所有的 Git 仓库都会使用这个配置,当然也可以对某个仓库指定不同的用户名和Email地址(不加 --global)

相关命令

(所有命令都在 Git Bash 中运行)
$ git                           查看 git 的相关命令 (git --help)
$ git --version                 查看 git 的版本
$ git config                    查看 git config 的相关命令
$ git pull origin develop       从远程(origin) 的 develop 分支拉取代码
  1. 初始化本地仓库: 在 Git Bash 中输入对应的命令

    $ cd Desktop  # cd: change directory 改变目录
    $ mkdir learngit   # mkdir:  创建目录
    $ cd learngit   
    $ pwd  # pwd:   用于显示当前目录
    
    # 注意: 为避免遇到各种奇怪的问题,确保目录名 (包括父目录) 不含中文
    # 不想要 git 管理跟踪的文件,可以在仓库根目录添加 .gitignore 文件,在里面写对应的规则
    $ git init              # 把当前目录初始化为 git 仓库
    $ ls -ah                # 查看当前目录下的文件,包含隐藏文件 (不带 -ah 看不了隐藏文件)
  2. 添加文件到仓库

    $ git add <file>              # 如: git add readme.txt
    $ git commit -m "description"     # 如: git commit -m "add readme.txt"

    添加文件到仓库分两步:

    1. add 添加该文件到仓库,
      • 添加许多同种类型的文件,可以使用通配符 * (记得加引号) 如: git add “*.txt” 命令就是添加所有 .txt 文件
    2. commit 提交该文件到仓库, description 为你对该次提交的描述说明,

    注意: 可以多次 add 不同的文件, commit 可以一次提交多个文件

  3. 查看仓库目前状态 (项目是否有修改、添加、未追踪的文件等)

    $ git status
  4. 查看修改内容,查看文件不同 (difference)

    $ git diff   # git diff 查看工作区(work dict)和暂存区(stage)的区别
    $ git diff <file>    # 如: git diff readme.txt  表示查看 readme.txt 修改了什么,有什么不同
    $ git diff --cached  # git diff --cached 查看暂存区(stage)和分支(master)的区别
    $ git diff HEAD -- <file>  # git diff HEAD -- <file> 查看工作区和版本库里面最新版本的区别
  5. 查看提交日志

    $ git log
    $ git log --oneline     # 美化输出信息,每个记录显示为一行,显示 commit_id 前几位数
    $ git log --pretty=oneline     # 美化输出信息,每个记录显示为一行,显示完整的 commit_id
    $ git log --graph --pretty=format:'%h -%d %s (%cr)' --abbrev-commit --
    $ git log --graph --pretty=oneline --abbrev-commit
    
    # 显示从最近到最远的提交日志
    # 日志输出一大串类似 3628164...882e1e0 的是commit_id (版本号),和 SVN 不一样,Git 的commit_id 不是 1,2,3…… 递增的数字,而是一个 SHA1 计算出来的一个非常大的数字,用十六进制表示, 因为 Git 是分布式的版本控制系统,当多人在同一个版本库里工作,如果大家都用 1,2,3……作为版本号,那就冲突了
    # 最后一个会打印出提交的时间等, (HEAD -> master)指向的是当前的版本
    # 退出查看 log 日志,输入字母 q
  6. 版本回退

    $ git reset --hard HEAD^
    $ git reset --hard <commit_id>
    
    # HEAD    表示当前版本,也就是最新的提交
    # HEAD^   上一个版本
    # HEAD^^  上上一个版本
    # HEAD~100   往上100个版本
    
    # 回退到上一个版本后, 用 git log 查看, 会发现当前版本消失
    # 若又想回到当前版本, 可以用 commit_id 指定回到未来的某个版本
    # 回退到 commit_id 对应的那个版本,commit_id 为版本号,只需要前几位就行
    
    Git的版本回退速度非常快,因为Git在内部有个指向当前版本的HEAD指针,当你回退版本的时候,Git仅仅是把HEAD从指向append GPL:
    
    ┌────┐
    │HEAD│
    └────┘
    │
    └──> ○ append GPL
        │
        ○ add distributed
        │
        ○ wrote a readme file
    改为指向add distributed:
    
    ┌────┐
    │HEAD│
    └────┘
    │
    │    ○ append GPL
    │    │
    └──> ○ add distributed
        │
        ○ wrote a readme file
    然后顺便把工作区的文件更新了. 所以你让HEAD指向哪个版本号,你就把当前版本定位在哪
  7. 查看命令历史 (用于版本切换)

    $ git reflog
    # 假如依次提交了三个版本 a->b->c,然后昨天从版本 c 回退到了版本 b,今天又想要回到版本 c,此时就可以使用 reflog 命令来查找 c 版本的 commit_id,然后使用 reset 命令来进行版本回退
  8. 撤销修改

    • 丢弃工作区 (Working Directory) 的修改

      $ git restore <file>  (建议使用) (如: git restore readme.txt)
      $ git checkout -- <file>
      # 命令中 -- 很重要,没有就变成 “切换到另一个分支” 的命令
    • 丢弃暂存区 (stage/index) 的修改

      # 第一步: 把暂存区的修改撤销掉(unstage),重新放回工作区
      $ git restore --staged <file>
          
      # 第二步: 撤销工作区的修改
      $ git restore <file>
    • 小结

      • 当你改乱了工作区某个文件的内容,想直接丢弃工作区的修改时,用命令git restore
      • 当你不但改乱了工作区某个文件的内容,还添加到了暂存区时,想丢弃修改,分两步,第一步用命令git restore –staged ,就回到了场景1,第二步按场景1操作
      • 已经提交了不合适的修改到版本库时,想要撤销本次提交,参考 6.版本回退,不过前提是没有推送到远程库
  9. 删除文件

    $ git rm <file>
    
    # git rm <file> 相当于执行
    - rm <file>
    - git add <file>
  10. 其他命令

    $ cat <file>    显示文件内容,如: cat readme.txt 就是在 git bash 中显示该文件内容
    $ cd ~         进入用户主目录
    $ open ~/.ssh  Mac 打开存放 ssh 文件夹

远程仓库

  1. 创建 SSH Key

    $ ssh-keygen -t rsa -C "youremail@example.com"
    # 邮件地址换成你自己的邮件地址,然后一路回车,使用默认值即可,由于这个Key也不是用于军事目的,所以也无需设置密码

    在用户主目录下,看看有没有.ssh目录,如果有,再看看这个目录下有没有id_rsa和id_rsa.pub这两个文件,如果已经有了,可直接跳到下一步。如果没有,打开Shell(Windows下打开Git Bash),创建SSH Key

    如果一切顺利的话,可以在用户主目录里找到.ssh目录,里面有 id_rsa 和 id_rsa.pub 两个文件,这两个就是 SSH Key 的秘钥对,id_rsa 是私钥,不能泄露出去,id_rsa.pub 是公钥,可以放心地告诉任何人

  2. 登录 GitHub ,在 Settings 中找到 SSH 设置项中添加新的 SSH Key,设置任意 title,在 Key 文本框里粘贴 id_rsa.pub 文件的内容

    # 复制Key用这种方式复制
    $ cd ~/.ssh
    $ cat id_rsa.pub
    
    $ open ~/.ssh   (Mac 下打开存放 Github 生成的 ssh Key 文件夹)
    
    $ pbcopy < ~/.ssh/id_rsa.pub  Mac 下拷贝生成的公钥内容
  3. 关联远程仓库 (先有本地仓库)

    $ git remote add origin git@github.com:charonnnn/learngit.git
    # 后面的地址换成自己的 GitHub 仓库地址
  4. 推送到远程仓库

    $ git remote       查看远程库信息
    $ git remote -v    查看远程库详细信息
    $ git remote rm origin  删除已关联的远程库 origin
    $ git push -u origin master    #第一次推送
    $ git push origin master      推送本地 master 分支到远程库
    $ git push origin dev         推送本地 dev 分支到远程库
    #  除了第一次推送,不需要添加 -u 参数
    
    # 一个本地库关联多个远程库,例如同时关联 GitHub 和 Gitee:
    # 1. 先关联GitHub的远程库:(注意:远程库的名称叫 github,不叫 origin)
    $ git remote add github git@github.com:charonnnn/learngit1.git
    # 2. 再关联Gitee的远程库:(注意:远程库的名称叫 gitee,不叫 origin)
    $ git remote add gitee git@gitee.com:charonnnn/learngit1.git
    # 3. 推送到远程库
    $ git push github master
    $ git push gitee master
    加上了-u参数,Git 不但会把本地的 master 分支内容推送的远程新的 master 分支,还会把本地的 master 分支和远程的master分支关联起来
  5. 从远程仓库克隆 (先有远程库)

    $ git clone git@github.com:charonnnn/gitskills.git
    # GitHub 支持多种协议,上面是 ssh 协议,还有 https 协议

分支

$ git branch       查看分支列表及当前分支
$ git branch dev   创建 dev 分支
$ git switch dev   切换到 dev 分支  (git checkout dev)
$ git switch -c dev   创建并切换到新的 dev 分支  (git checkout -b dev)
$ git switch -c dev origin/dev  创建远程 origin 的 dev 分支到本地并切换到该分支
$ git branch -d dev   删除 dev 分支
$ git branch -D dev   强制删除 dev 分支
$ git merge dev       合并 dev 分支到当前分支 (当有冲突的时候,需要先解决冲突)
$ git merge --no-ff -m "merge with no-ff" dev  合并 dev 分支到当前分支(禁用Fast forward 合并策略)

$ git pull  拉取远程分支最新的内容
$ git branch --set-upstream-to=origin/dev dev  指定本地 dev 分支与远程 origin/dev 分支的链接

# 为本次合并要创建一个新的commit,所以加上-m参数,把commit描述写进去
# 合并分支时,加上--no-ff参数就可以用普通模式合并,合并后的历史有分支,能看出来曾经做过合并,而 fast forward 合并就看不出来曾经做过合并

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

$ git stash  保存当前工作区和暂存区的修改状态,切换到其他分支修复 bug 等工作,然后在回来继续工作
$ git stash list  查看保存现场的列表
$ git stash pop   恢复的同时把 stash 内容也删除
$ git stash apply  恢复现场,stash内容并不删除
$ git stash drop   删除 stash 内容
$ git stash apply stash@{0}  多次stash,恢复的时候,先用git stash list查看,然后恢复指定的stash
# 通常在 dev 分支开发时,需要有紧急 bug 需要马上处理,保存现在修改的文件等,先修复 bug 后再回来继续工作的情况

$ git cherry-pick <commit> 复制一个特定的提交到当前分支(当前分支的内容需要先 commit,然后冲突的文件需要解决冲突,然后 commit)

$ git rebase  把本地未push的分叉提交历史整理成直线(使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比)

标签

发布一个版本时, 通常先在版本库中打一个标签(tag),这样就唯一确定了打标签时刻的版本. 将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来. 所以,标签也是版本库的一个快照.

Git的标签虽然是版本库的快照,但其实它就是指向某个commit的指针(跟分支很像, 但是分支可以移动,标签不能移动),所以创建和删除标签都是瞬间完成的

  • Q: Git有commit,为什么还要引入tag?
  • A: tag 比 commit_id 好认, 它实际上和commit_id绑定在一起

    # 切换到对应的分支 branch 上,查看或者操作对应的标签 tag 
    $ git tag  查看所有的标签
    $ git tag <tagname>  打标签(默认标签是打在最新提交的commit上) 如: git tag v1.0
    $ git tag <tagname> <commit_id>  给对应的 commit_id 打标签
    $ git tag -a <tagname> -m "标签说明信息" <commit_id> 创建带有说明的标签,用-a指定标签名,-m指定说明文字
    $ git tag -d <tagname> 删除一个本地标签
    $ git push origin :refs/tags/<tagname>可以删除一个远程标签
    $ git show <tagname>  查看标签信息
    
    $ git push origin <tagname>  推送一个本地标签到远程
    $ git push origin --tags     一次性推送全部尚未推送到远程的本地标签
    
    # 删除远程标签,需要先删除本地标签,然后在删除远程标签,如:删除标签 v0.9
    $ git tag -d v0.9
    $ git push origin :refs/tags/v0.9

Git 常用command

一. 新建代码库

# 在当前目录新建一个Git代码库 
$ git init

# 新建一个目录,将其初始化为Git代码库 
$ git init [project-name]

# 下载一个项目和它的整个代码历史 
$ git clone [url]

二. 配置

Git的设置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)

# 显示当前的Git配置
$ git config --list # 编辑Git配置文件
$ git config -e [--global]

# 设置提交代码时的用户信息
$ git config [--global] user.name "[name]"
$ git config [--global] user.email "[email address]"

三. 增加/删除文件

# 添加指定文件到暂存区
$ git add [file1] [file2] ... # 添加指定目录到暂存区,包括子目录
$ git add [dir]

# 添加当前目录的所有文件到暂存区
$ git add .

# 添加每个变化前,都会要求确认
# 对于同一个文件的多处变化,可以实现分次提交 
$ git add -p

# 删除工作区文件,并且将这次删除放入暂存区 
$ git rm [file1] [file2] ...

# 停止追踪指定文件,但该文件会保留在工作区 
$ git rm --cached [file]

# 改名文件,并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]

四. 代码提交

# 提交暂存区到仓库区
$ git commit -m [message]

# 提交暂存区的指定文件到仓库区
$ git commit [file1] [file2] ... -m [message]

# 提交工作区自上次commit之后的变化,直接到仓库区 
$ git commit -a

# 提交时显示所有diff信息 
$ git commit -v

# 使用一次新的commit,替代上一次提交
# 如果代码没有任何新变化,则用来改写上一次commit的提交信息 
$ git commit --amend -m [message]

# 重做上一次commit,并包括指定文件的新变化
$ git commit --amend [file1] [file2] ...

五. 分支

# 列出所有本地分支 
$ git branch

# 列出所有远程分支 
$ git branch -r

# 列出所有本地分支和远程分支 
$ git branch -a

# 新建一个分支,但依然停留在当前分支 
$ git branch [branch-name]

# 新建一个分支,并切换到该分支 
$ git checkout -b [branch]

# 新建一个分支,指向指定commit
$ git branch [branch] [commit]

# 新建一个分支,与指定的远程分支建立追踪关系
$ git branch --track [branch] [remote-branch] 

# 切换到指定分支,并更新工作区
$ git checkout [branch-name] 

# 切换到上一个分支
$ git checkout -

# 建立追踪关系,在现有分支与指定的远程分支之间
$ git branch --set-upstream [branch] [remote-branch] 

# 合并指定分支到当前分支
$ git merge [branch]

# 选择一个commit,合并进当前分支
$ git cherry-pick [commit] 

# 删除分支
$ git branch -d [branch-name]

# 删除远程分支
$ git push origin --delete [branch-name] 
$ git branch -dr [remote/branch]

六. 标签

# 列出所有tag 
$ git tag

# 新建一个tag在当前commit 
$ git tag [tag]

# 新建一个tag在指定commit 
$ git tag [tag] [commit]

# 删除本地tag
$ git tag -d [tag]

# 删除远程tag
$ git push origin :refs/tags/[tagName]

# 查看tag信息
$ git show [tag]

# 提交指定tag
$ git push [remote] [tag]

# 提交所有tag
$ git push [remote] --tags

# 新建一个分支,指向某个tag
$ git checkout -b [branch] [tag]

七. 查看信息

# 显示有变更的文件 
$ git status

# 显示当前分支的版本历史 
$ git log

# 显示commit历史,以及每次commit发生变更的文件 
$ git log --stat

# 搜索提交历史,根据关键词 
$ git log -S [keyword]

# 显示某个commit之后的所有变动,每个commit占据一行 
$ git log [tag] HEAD --pretty=format:%s

# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件 
$ git log [tag] HEAD --grep feature

# 显示某个文件的版本历史,包括文件改名 
$ git log --follow [file]
$ git whatchanged [file]

# 显示指定文件相关的每一次diff 
$ git log -p [file]

# 显示过去5次提交
$ git log -5 --pretty --oneline

# 显示所有提交过的用户,按提交次数排序 
$ git shortlog -sn

# 显示指定文件是什么人在什么时间修改过 
$ git blame [file]

# 显示暂存区和工作区的差异 
$ git diff

# 显示暂存区和上一个commit的差异 
$ git diff --cached [file]

# 显示工作区与当前分支最新commit之间的差异 
$ git diff HEAD

# 显示两次提交之间的差异
$ git diff [first-branch]...[second-branch]

# 显示今天你写了多少行代码
$ git diff --shortstat "@{0 day ago}"

# 显示某次提交的元数据和内容变化 
$ git show [commit]

# 显示某次提交发生变化的文件
$ git show --name-only [commit]

# 显示某次提交时,某个文件的内容
$ git show [commit]:[filename]

# 显示当前分支的最近几次提交 
$ git reflog

八. 远程同步

# 下载远程仓库的所有变动 
$ git fetch [remote]

# 显示所有远程仓库 
$ git remote -v

# 显示某个远程仓库的信息
$ git remote show [remote]

# 增加一个新的远程仓库,并命名
$ git remote add [shortname] [url]

# 取回远程仓库的变化,并与本地分支合并 
$ git pull [remote] [branch]

# 上传本地指定分支到远程仓库
$ git push [remote] [branch]

# 强行推送当前分支到远程仓库,即使有冲突 
$ git push [remote] --force

# 推送所有分支到远程仓库
$ git push [remote] --all

九. 撤销

# 恢复暂存区的指定文件到工作区 
$ git checkout [file]

# 恢复某个commit的指定文件到暂存区和工作区 
$ git checkout [commit] [file]

# 恢复暂存区的所有文件到工作区 
$ git checkout .

# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变 
$ git reset [file]

# 重置暂存区与工作区,与上一次commit保持一致 
$ git reset --hard

# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变 
$ git reset [commit]

# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
$ git reset --hard [commit]

# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
$ git reset --keep [commit]

# 新建一个commit,用来撤销指定commit
# 后者的所有变化都将被前者抵消,并且应用到当前分支 
$ git revert [commit]

# 暂时将未提交的变化移除,稍后再移入 
$ git stash
$ git stash pop

十. 其他

# 生成一个可供发布的压缩包 
$ git archive

Mac中Git忽略.DS_Store文件

.DS_Store是Mac OS用来存储这个文件夹的显示属性的,被作为一种通用的有关显示设置的元数据(比如图标位置等设置)为Finder、Spotlight用, 所以在不经意间就会修改这个文件. 而文件共享时为了隐私关系将.DS_Store文件删除比较好,因为其中有一些信息在不经意间泄露出去.

Git中处理方案

方案一:项目设置.gitignore

仅针对git的处理最naive的想法就是设置.gitignore文件。

.gitignore文件用于忽略文件,规范如下:

  • 所有空行或者以注释符号 开头的行都会被 git 忽略,空行可以为了可读性分隔段落,# 表明注释
  • 第一个 / 会匹配路径的根目录,举个例子,"/*.html"会匹配"index.html",而不是"d/index.html"
  • 通配符 * 匹配任意个任意字符,? 匹配一个任意字符. 需要注意的是通配符不会匹配文件路径中的 /,举个例子,”d/*.html”会匹配”d/index.html”,但不会匹配”d/a/b/c/index.html”
  • 两个连续的星号 ** 有特殊含义:
    • **/ 开头表示匹配所有的文件夹,例如 **/test.md 匹配所有的test.md文件
    • /** 结尾表示匹配文件夹内所有内容,例如 a/**匹配文件夹a中所有内容
    • 连续星号 ** 前后分别被 / 夹住表示匹配0或者多层文件夹,例如 a/**/b 匹配到 a/ba/x/ba/x/y/b
  • 前缀 ! 的模式表示如果前面匹配到被忽略,则重新添加回来. 如果匹配到的父文件夹还是忽略状态,该文件还是保持忽略状态. 如果路径名第一个字符为 ! ,则需要在前面增加 \ 进行转义
  • 对于一些常用的系统、工程文件的.gitignore文件可以参考这个网站进行设置,这里有很多模板.

针对.DS_Store文件,在git工程文件夹中新建.gitignore文件,在文件中设置:

.gitignore
**/.DS_Store

对于已经提交的内容,希望git能够忽略,但同时并不会删除本地文件,需要在terminal输入以下命令:

$ git rm -r --cached $file_path/.DS_Store

这个方案的优点就是方便、快捷、最容易想到,缺点就是每个git项目都要重复一遍.

方案二:全局设置忽略

虽然每个项目配.gitignore文件可以成功,但是麻烦, 需要每个项目进行配置. 可以在git的全局进行配置来忽略.DS_Store文件.

设置之前先看下现在的git config配置情况:

$ git config --list

实际上git配置情况可以在 ~/.gitconfig 文件中查看.

$ vi ~/.gitconfig

通过 :q! 退出后,需要建立一个文件,把需要全局忽略的文件路径写入其中. 该文件起名为.gitignore_global

$ touch ~/.gitignore_global

然后对这个文件进行修改.

# Mac OS
**/.DS_Store

然后对git进行全局设置,让git忽略.gitignore_global中的所有文件:

$ git config --global core.excludesfile ~/.gitignore_global

这样就不用每个git目录都设置忽略.DS_Store文件了.