Git

本人总结的 Git 教程


简介

Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。

Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

Git 与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,不必服务器端软件支持。

Git Official: https://git-scm.com/

Git Documentation: https://git-scm.com/doc

Git Reference Guide: https://git-scm.com/docs

Git Book: https://git-scm.com/book/en/v2

安装

各系统安装官方说明: https://git-scm.com/downloads

Older releases are available and the Git source repository is on GitHub.

git [--version] [--help] [-C <path>] [-c <name>=<value>]
    [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
    [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
    [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
    [--super-prefix=<path>] [--config-env=<name>=<envvar>]
    <command> [<args>]

Git 具有异常丰富的命令集,可提供高级操作和对内部的完全访问。

掌握基本命令后,可以通过“git help command”了解更多关于单个 Git 命令的信息。

配置

Git 提供了一个叫做 git config 的工具,专门用来配置或读取相应的工作环境变量。

这些环境变量,决定了 Git 在各个环节的具体工作方式和行为。这些变量可以存放在以下三个不同的地方:

  1. /etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用 git config 时用 --system 选项,读写的就是这个文件。
  2. ~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用 git config 时用 --global 选项,读写的就是这个文件。
  3. 当前项目的 Git 目录中的配置文件(也就是工作目录中的 .git/config 文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 /etc/gitconfig 中的同名变量。

在 Windows 系统上,Git 会找寻用户主目录下的 .gitconfig 文件。主目录即 $HOME 变量指定的目录,一般都是 C:\Documents and Settings\$USER

常用的配置命令如下:

# 配置个人的用户名称和电子邮件地址:
$ git config --global user.name "runoob"
$ git config --global user.email test@runoob.com

# 设置Git默认使用的文本编辑器, 一般可能会是 `Vi` 或者 `Vim`。如果你有其他偏好,比如 `Emacs` 的话,可以重新设置:
$ git config --global core.editor emacs

# 还有一个比较常用的是,在解决合并冲突时使用哪种差异分析工具。比如要改用 `vimdiff` 的话:
# Git 可以理解 kdiff3,tkdiff,meld,xxdiff,emerge,vimdiff,gvimdiff,ecmerge,和 opendiff 等合并工具的输出信息。
$ git config --global merge.tool vimdiff

# 要检查已有的配置信息,可以使用命令:
$ git config --list
http.postbuffer=2M
user.name=runoob
user.email=test@runoob.com

# 可以直接查询某个变量的设定,像这样:
$ git config user.name
runoob

如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 --global 选项重新配置即可,新的设定保存在当前项目的 .git/config 文件里。

有时候会看到重复的变量名,那就说明它们来自不同的配置文件(比如 /etc/gitconfig~/.gitconfig),不过最终 Git 实际采用的是最后一个。

这些配置我们也可以在 ~/.gitconfig/etc/gitconfig 也能看到。

流程

初始化

git-init - 创建一个空的 Git 存储库或重新初始化现有的

git init [-q | --quiet] [--bare] [--template=<template_directory>]
      [--separate-git-dir <git dir>] [--object-format=<format>]
      [-b <branch-name> | --initial-branch=<branch-name>]
      [--shared[=<permissions>]] [directory]

该命令创建一个空的Git存储库 - 本质上是一个 .git 目录,其中包含 objectsrefs/headsrefs/tags和模板文件的子目录。还将创建一个引用master分支 HEAD 的初始 HEAD 文件。

  • --bare : 创建一个纯仓库。如果未设置 GIT_DIR 环境变量,则将其设置为当前工作目录。
  • --object-format=<format> : 指定存储库的对象格式(哈希算法)。有效值为 sha1 和(如果启用)sha256sha1 是默认值。
  • --template=<template_directory> : 指定要使用模板的目录。
  • -b <branch-name> | --initial-branch=<branch-name> : 在新创建的仓库中为初始分支指定名称。如果没有指定,则使用默认名称:master
  • --shared : 指定 Git 存储库在多个用户之间共享。

参考: https://git-scm.com/docs/git-init

克隆

将存储库克隆到新创建的目录中,为克隆存储库中的每个分支创建远程跟踪分支:

git clone [--template=<template-directory>]
      [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
      [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
      [--dissociate] [--separate-git-dir <git-dir>]
      [--depth <depth>] [--[no-]single-branch] [--no-tags]
      [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules]
      [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow]
      [--filter=<filter> [--also-filter-submodules]] [--] <repository>
      [<directory>]
  • git clone -q , --quiet 【远程仓库地址】: 安静地操作。进展不会报告给标准错误流。
  • git clone -n, --no-checkout【远程仓库地址】: 克隆完成后不会执行 HEAD 的检出。
  • git clone --bare 【远程仓库地址】: 制作一个裸露的Git存储库。 也就是说,不要创建 <directory> 并将管理文件放置在 <directory>/.git 中,而是将 <directory> 本身设置为 $GIT_DIR 。 显然这意味着 --no-checkout ,因为没有地方可以检出工作树。 同样,将远程的分支头直接复制到相应的本地分支头,而无需将其映射到 ref/remotes/origin/ 。 使用此选项时,不会创建远程跟踪分支或相关的配置变量。
  • git clone --mirror 【远程仓库地址】: 设置源存储库的镜像。 这意味着裸露(--bare)。 与--bare相比,--mirror不仅将源的本地分支映射到目标的本地分支,还映射所有引用(包括远程跟踪分支,注释等),并设置一个refspec配置,以便所有这些引用 被目标存储库中的git远程更新覆盖。
  • git clone -b, --branch 【远程仓库地址】: 不要将新创建的HEAD指向克隆的存储库的HEAD所指向的分支,而是指向分支。
  • git clone --depth 【远程仓库地址】: 创建一个浅克隆,其历史记录被截断为指定的提交数。 表示 --single-branch ,除非给出 --no-single-branch 来获取所有分支的尖端附近的历史记录。 如果要浅层克隆子模块,则还要传递 --shallow-submodules

参考: https://git-scm.com/docs/git-clone

提交

git-commit - 记录对存储库的更改

git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]
       [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>)]
       [-F <file> | -m <msg>] [--reset-author] [--allow-empty]
       [--allow-empty-message] [--no-verify] [-e] [--author=<author>]
       [--date=<date>] [--cleanup=<mode>] [--[no-]status]
       [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]
       [(--trailer <token>[(=|:)<value>])…​] [-S[<keyid>]]
       [--] [<pathspec>…​]
  • -F : 从给定文件中获取提交消息。使用-从标准输入读取消息。
  • -m : 使用给定的 作为提交消息。如果给出了多个 -m 选项,它们的值将连接为单独的段落。-m 选项与-c-C-F互斥。

参考: https://git-scm.com/docs/git-commit

推送

git-push - 更新远程引用以及关联对象

git push [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
       [--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-v | --verbose]
       [-u | --set-upstream] [-o <string> | --push-option=<string>]
       [--[no-]signed|--signed=(true|false|if-asked)]
       [--force-with-lease[=<refname>[:<expect>]] [--force-if-includes]]
       [--no-verify] [<repository> [<refspec>…​]]
  • --tags : 除了在命令行上明确列出的 refspecs 之外,refs/tags 所有的 refs 都会被推送。
  • --atomic : 如果可用,请在远程端使用原子事务。要么更新所有 refs,要么在错误时不更新 refs。如果服务器不支持原子推送,推送将失败。
  • -u | --set-upstream : 对于每个最新或成功推送的分支,使用无参数 git-pull和其他命令,添加上游(跟踪)引用。

参考: https://git-scm.com/docs/git-push

更新

git-pull - 将来自远程存储库的更改合并到当前分支中

git pull [<options>] [<repository> [<refspec>…​]]

参考: https://git-scm.com/docs/git-pull

分支

git-branch - 列出、创建或删除分支

git branch [--color[=<when>] | --no-color] [--show-current]
    [-v [--abbrev=<n> | --no-abbrev]]
    [--column[=<options>] | --no-column] [--sort=<key>]
    [--merged [<commit>]] [--no-merged [<commit>]]
    [--contains [<commit>]] [--no-contains [<commit>]]
    [--points-at <object>] [--format=<format>]
    [(-r | --remotes) | (-a | --all)]
    [--list] [<pattern>…​]
git branch [--track[=(direct|inherit)] | --no-track] [-f]
    [--recurse-submodules] <branchname> [<start-point>]
git branch (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
git branch --unset-upstream [<branchname>]
git branch (-m | -M) [<oldbranch>] <newbranch>
git branch (-c | -C) [<oldbranch>] <newbranch>
git branch (-d | -D) [-r] <branchname>…​
git branch --edit-description [<branchname>]

如果给出--list,或者没有非选项参数,则列出现有分支;当前分支将以绿色突出显示并标有星号。在链接的工作树中签出的任何分支都将以青色突出显示并标有加号。选项-r导致列出远程跟踪分支,选项-a显示本地和远程分支。

如果<pattern>给定了 a,则将其用作 shell 通配符以将输出限制为匹配的分支。如果给出了多个模式,则如果它与任何模式匹配,则会显示一个分支。

请注意,提供 <pattern> 时,您必须使用--list; 否则该命令可能被解释为分支创建。

该命令的第二种形式创建一个名为 <branchname> 的新分支头,它指向当前的HEAD,或者给定的 <start-point> 。作为一种特殊情况,对于 <start-point>,您可以将"A...B"其用作合并基础的快捷方式,A并且B如果只有一个合并基础。您最多可以省略一个A and B,在这种情况下它默认为 HEAD.

请注意,这将创建新分支,但不会将工作树切换到它;使用“git switch ”切换到新分支。

使用-m或者-M选项, 将被重命名为 。如果 有相应的 reflog,则将其重命名以匹配 ,并创建一个 reflog 条目以记住分支重命名。如果 存在,则必须使用 -M 来强制进行重命名。

-c-C选项与-m-M具有完全相同的语义,除了分支不会被重命名,它将被复制到一个新名称,以及它的配置和 reflog。

带有-d-D选项,将被删除。您可以指定多个要删除的分支。如果分支当前有 reflog,则 reflog 也将被删除。

-r-d一起使用删除远程跟踪分支。请注意,只有在远程存储库中不再存在远程跟踪分支或git fetch配置为不再获取它们时,删除远程跟踪分支才有意义。

参考: https://git-scm.com/docs/git-branch

标签

git-tag - 创建、列出、删除或使用 GPG 签名的验证标签对象

git tag [-a | -s | -u <keyid>] [-f] [-m <msg> | -F <file>] [-e]
    <tagname> [<commit> | <object>]
git tag -d <tagname>…​
git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]
    [--points-at <object>] [--column[=<options>] | --no-column]
    [--create-reflog] [--sort=<key>] [--format=<format>]
    [--merged <commit>] [--no-merged <commit>] [<pattern>…​]
git tag -v [--format=<format>] <tagname>…​

refs/tags/ 中添加标签引用,除非-d/-l/-v给出删除、列出或验证标签。

除非给出-f,否则命名标签必须不存在。

如果传递了 -a-s-u <keyid>中的一个,则该命令将创建一个标记对象,并需要一个标记消息。除非给出 -m <msg>-F <file>,否则将启动一个编辑器供用户键入标签消息。

如果-m <msg>-F <file>被给出并且-a, -s, 和-u <keyid> 不存在,那么-a是隐含的。

否则,将创建直接指向给定对象的标记引用(即轻量级标记)。

一个 GnuPG 签名的标签对象将在-s-u <keyid>使用时创建。不使用-u <keyid>时,当前用户的提交者身份用于查找 GnuPG 密钥进行签名。配置变量 gpg.program 用于指定自定义 GnuPG 二进制文件。

标记对象(用 -a-s-u创建)称为“带注释的”标签(annotated tag);它们包含创建日期、标记名称和电子邮件、标记消息和可选的 GnuPG 签名。而“轻量级”标签(lightweight tag)只是对象(通常是提交对象)的简单名称。

带注释的标签用于发布,而轻量级标签用于私有或临时对象标签。出于这个原因,一些用于命名对象的 git 命令(如git describe)默认会忽略轻量级标签。

在我们使用 git push 命令推送某个分支的时候,标签并不会被推送到远程仓库,所以我们必须显式的推送标签到远程仓库

使用下面两种命令则可以推送标签到远程仓库:

  • git push <remote> <tag_name>:推送某个标签到远程仓库。
  • git push <remote> --tags:推送所有标签到远程仓库。

同样,在删除本地的标签后,要想删除远程仓库的标签,也必须使用的显式的命令:

  • git push <remote> --delete <tag_name>:删除远程仓库中的某个标签。
  • git push <remote> :refs/tags/<tagname>:删除远程仓库某个标签的等价方式,相当于将冒号前面的空值推送到远程标签名,从而高效地删除它。

参考: https://git-scm.com/docs/git-tag

合并

git-merge - 将两个或多个开发历史连接在一起

git merge [-n] [--stat] [--no-commit] [--squash] [--[no-]edit] 
    [--no-verify] [-s <strategy>] [-X < strategy-option>] [-S[<keyid>]] 
    [--[no-]allow-unrelated-histories] 
    [--[no-]rerere-autoupdate] [-m <msg>] [-F <file >] 
    [--into-name <branch>] [<commit>...​] git merge (--continue | --abort | --quit)

在应用外部更改之前,您应该让自己的工作保持良好状态并在本地提交,这样如果发生冲突就不会被破坏。另见git-stash。 当本地未提交的更改与git pull / git merge可能需要更新的文件重叠时,git pullgit merge将停止而不做任何事情。

看到冲突后,您可以做两件事:

  • 决定不合并。您需要的唯一清理是将索引文件重置为HEAD的反向提交, 并清理所做的工作树更改;git merge --abort 可用于此。
  • 解决冲突。Git 将标记工作树中的冲突。将文件编辑成形状 并将它们添加到索引中。使用 git commitgit merge --continue来完成交易。后一个命令在调用git commit之前检查是否正在进行(中断的)合并。

您可以使用多种工具解决冲突:

  • 使用合并工具。 git mergetool启动图形合并工具,它将帮助您完成合并。
  • 看看差异。 git diff将显示三向差异,突出显示HEADMERGE_HEAD 版本的更改。
  • 查看每个分支的差异。git log --merge -p <path> 将首先显示HEAD版本的差异,然后显示 MERGE_HEAD版本。
  • 看看原件。 git show :1:filename显示共同祖先,git show :2:filename显示HEAD 版本,git show :3:filename显示MERGE_HEAD 版本。

参考: https://git-scm.com/docs/git-merge

暂存stash

git-stash - 将更改存储在脏工作目录中

git stash list [<log-options>]
git stash show [-u|--include-untracked|--only-untracked] [<diff-options>] [<stash>]
git stash drop [-q|--quiet] [<stash>]
git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]
git stash branch <branchname> [<stash>]
git stash [push [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]
         [-u|--include-untracked] [-a|--all] [-m|--message <message>]
         [--pathspec-from-file=<file> [--pathspec-file-nul]]
         [--] [<pathspec>…​]]
git stash clear
git stash create [<message>]
git stash store [-m|--message <message>] [-q|--quiet] <commit>

当您想记录工作目录和索引的当前状态,但又想回到干净的工作目录时使用git stash。该命令保存您的本地修改并恢复工作目录以匹配HEAD提交。

该命令隐藏的修改可以用 git stash list 列出 、git stash show检查和git stash apply恢复(可能在不同的提交之上)。git stash不带任何参数的调用等效于git stash push. 默认情况下,存储被列为“WIP on branchname ...​”,但您可以在创建时在命令行上提供更具描述性的消息。

您创建的最新存储存储在refs/stash; 较旧的存储可在此参考的 reflog 中找到,并且可以使用通常的 reflog 语法命名(例如stash@{0},是最近创建的存储,stash@{1}是它之前的存储,stash@{2.hours.ago} 也是可能的)。也可以通过仅指定存储索引来引用存储(例如,整数n等价于stash@{n})。

  • push [-m | --message ] [--] […​ ] : 将本地修改保存到新的存储条目并将它们回滚到 HEAD(在工作树和索引中)。<message> 部分是可选的,它提供描述以及隐藏状态。
  • save : 此选项已弃用,取而代之的是git stash push。它与“stash push”的不同之处在于它不能采用 pathspec。相反,所有非选项参数都连接起来形成存储消息。
  • list [] : 列出您当前拥有的存储条目。每个存储条目都列出了它的名称(例如stash@{0},是最新的条目,stash@{1}是之前的条目,等等),创建条目时当前分支的名称,以及该条目所基于的提交的简短描述.

    stash@{0}: WIP on submit: 6ebd0e2... Update git-stash documentation
    stash@{1}: On master: 9cc0589... Add git-stash
    
  • show [-u|--include-untracked|--only-untracked] [] [] : 将存储条目中记录的更改显示为存储内容与首次创建存储条目时返回的提交之间的差异。默认情况下,该命令显示 diffstat,但它会接受git diff已知的任何格式(例如,git stash show -p stash@{1} 以补丁形式查看第二个最近的条目)。如果没有<diff-option>提供,默认行为将由config 变量stash.showStatstash.showPatch给出。您也可以使用stash.showIncludeUntracked设置 --include-untracked选项是否默认启用。

  • pop [--index] [-q|--quiet] [] : 从存储列表中删除单个隐藏状态并将其应用于当前工作树状态的顶部,即执行 git stash push 的逆操作。工作目录必须与索引匹配。应用状态可能会因冲突而失败;在这种情况下,它不会从存储列表中删除。您需要手动解决冲突,然后手动调用git stash drop
  • apply [--index] [-q|--quiet] [] : 类似pop,但不会从stash存储列表中删除状态。不同于pop, 可能是任何看起来像由stash pushstash create创建的提交。
  • branch [] : 创建并签出一个从<stash>最初创建的提交开始命名的新分支<branchname>,将<stash>记录的更改应用到新的工作树和索引。如果成功,并且<stash>是表单的引用 stash@{<revision>},则删除<stash>。如果您运行git stash push的分支发生了足够多的更改而导致git stash apply由于冲突而失败,这将很有用。由于存储条目应用在运行git stash时为 HEAD 的提交之上,因此它恢复了最初的stash状态而没有冲突。
  • clear : 删除所有stash条目。请注意,这些条目随后将受到修剪,并且可能无法恢复。
  • drop [-q|--quiet] [] : 从stash列表中删除单个stash。
  • create : 创建一个stash(这是一个常规提交对象)并返回其对象名称,而不将其存储在 ref 命名空间中的任何位置。这旨在对脚本有用。它可能不是您要使用的命令;参见上面的“push”。
  • store : 将通过git stash create 创建的给定存储(这是一个悬空合并提交)存储在存储引用中,更新存储引用日志。这旨在对脚本有用。它可能不是您要使用的命令;参见上面的“push”。

参考: https://git-scm.com/docs/git-stash

比较diff

git-diff - 显示提交、提交和工作树等之间的更改

git diff [<options>] [<commit>] [--] [<path>…​]
git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>…​]
git diff [<options>] [--merge-base] <commit> [<commit>…​] <commit> [--] [<path>…​]
git diff [<options>] <commit>…​<commit> [--] [<path>…​]
git diff [<options>] <blob> <blob>
git diff [<options>] --no-index [--] <path> <path>

参考: https://git-scm.com/docs/git-diff

查找grep

git-grep - 输出与查找模式匹配的行

git grep [-a | --text] [-I] [--textconv] [-i | --ignore-case] [-w | --word-regexp]
       [-v | --invert-match] [-h|-H] [--full-name]
       [-E | --extended-regexp] [-G | --basic-regexp]
       [-P | --perl-regexp]
       [-F | --fixed-strings] [-n | --line-number] [--column]
       [-l | --files-with-matches] [-L | --files-without-match]
       [(-O | --open-files-in-pager) [<pager>]]
       [-z | --null]
       [ -o | --only-matching ] [-c | --count] [--all-match] [-q | --quiet]
       [--max-depth <depth>] [--[no-]recursive]
       [--color[=<when>] | --no-color]
       [--break] [--heading] [-p | --show-function]
       [-A <post-context>] [-B <pre-context>] [-C <context>]
       [-W | --function-context]
       [--threads <num>]
       [-f <file>] [-e] <pattern>
       [--and|--or|--not|(|)|-e <pattern>…​]
       [--recurse-submodules] [--parent-basename <basename>]
       [ [--[no-]exclude-standard] [--cached | --no-index | --untracked] | <tree>…​]
       [--] [<pathspec>…​]

参考: https://git-scm.com/docs/git-grep

示例

下面是本人实际遇到的一些示例:

服务器搭建

本地自建git服务器

jerry@jerry:~$ git init --bare demo.git
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint: 
hint:   git config --global init.defaultBranch <name>
hint: 
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint: 
hint:   git branch -m <name>
Initialized empty Git repository in /home/jerry/demo.git/

# 在另外一个文件夹下克隆此仓库
jerry@jerry:~/Code$ git clone jerry@127.0.0.1:/home/jerry/demo.git
Cloning into 'demo'...
jerry@127.0.0.1's password: 
warning: You appear to have cloned an empty repository.

clone格式:
$ git clone ssh://gituser@www.xx.com:27852/xx/git/xx/sample.git
其中 gituser:表示用户 www.xx.com:表示服务ip地址 27852表示端口 /xx/git/xx/sample.git:表示代码仓库路径

ssh免密

生成RSA密钥:

$ ssh-keygen -t rsa -C "1364812552@qq.com"

-t 表示密钥的类型 ,-b表示密钥的长度,-C 用于识别这个密钥的注释。

linux默认生成目录是~/.ssh/,Windows是C:\Users\Administrator\.ssh\

复制公钥id_rsa.pub的内容,在Github -> Settings -> SSH and GPG keys -> New SSH key 中添加公钥。

使用ssh -T git@github.com验证是否成功。

只克隆单个文件夹

以https://github.com/geekhac/to...子目录为例:

$ git init todomvc && cd todomvc
$ git config core.sparsecheckout true //设置允许克隆子目录
$ echo '/examples/react/*' >> .git/info/sparse-checkout //设置要克隆的仓库的子目录路径
$ git remote add origin https://github.com/geekhac/to...
$ git pull origin master

/examples/react/*路径可能需要相对路径,经本人测试,是否以'/'开头不影响操作结果。

代理设置

# 设置ss
$ git config --global http.proxy 'socks5://127.0.0.1:1080'
$ git config --global https.proxy 'socks5://127.0.0.1:1080'
# 设置代理
$ git config --global http.proxy http://127.0.0.1:1080
$ git config --global https.proxy https://127.0.0.1:1080
# 取消代理
$ git config --global --unset http.proxy
$ git config --global --unset https.proxy

http缓冲区

当仓库超过1G时,HTTPS Clone和下载ZIP文件可能会失败,使用SSH Clone更好。 或者在增大postBuffer的同时,关闭ssl认证:

$ git config --global http.postBuffer 2048000000 # 设置为2G
$ git config --global http.sslVerify false # 关闭sslVerify
$ git config --global core.compression 0 # or -1

启用长路经

git有可以创建4096长度的文件名,然而在windows最多是260,因为git用了旧版本的windows api,为此踩了个坑。

$ git checkout -f HEAD
fatal: cannot create directory at 'src/third_party/node/linux/node-linux-x64/lib/node_modules/npm/node_modules/libnpx/node_modules/yargs/node_modules/read-pkg-up/node_modules/read-pkg/node_modules/load-json-file/node_modules/parse-json/node_modules/error-ex/node_modules': Filename too long
$ git config --global core.longpaths true

分支合并

git merge最简洁用法

  • 开发分支(dev)上的代码达到上线的标准后,要合并到 master 分支

    $ git checkout dev
    $ git pull
    $ git checkout master
    $ git merge dev
    $ git push -u origin master
    
  • 当master代码改动了,需要更新开发分支(dev)上的代码

    $ git checkout master 
    $ git pull 
    $ git checkout dev
    $ git merge master 
    $ git push -u origin dev
    

在某个远程分支的基础上创建自己的分支

$ git checkout -b 本地分支名 origin/远程分支名
$ git checkout -b develop origin/develop
# 将自己创建的本地分支提交到远程分支,自动在远程分支上创建自己的分支
$ git push origin 本地分支名:远端分支名xx  // 在服務器新建新分支名xxx
$ git push origin develop:develop

打本地补丁

只想将当前文件夹中改动的文件打包成一个patch,并不想提交相应的代码

$ git diff > xxx.patch

当再次用这个patch的时候,执行命令

$ git apply xxx.patch

查看commit提交记录详情

# 查看最新的commit
$ git show
# 查看指定commit hashID的所有修改:
$ git show commitId
# 查看某次commit中具体某个文件的修改:
$ git show commitId fileName

对提交的补救

当我们提交(commit)后,还未push前,发现有些文件忘了暂存了,这些文件自然也不可能被上传。此时需要补救措施:即将这些文件add上,重新commit一下。 假设文件forget.text是被遗忘的文件:

# 第一次提交(忘了文件)
$ git commit -m "commit-first-time"
# 将忘了的文件继续add上
$ git add forget.txt
# 重新提交一次,这样就能够将之间文件和忘了的文件一起提交了
$ git commit --amend

暂存staged

# 暂存一个文件test.txt
$ git add test.txt
# 发现该文件不需要暂存,那就撤销吧
$ git reset HEAD test.txt
# 不想要这些修改了,丢弃修改,执行完后回到最近的暂存状态,如果没有暂存文件,则回退到最新的版本
$ git checkout -- test.txt

查看历史提交记录

$ git log -- <file>
# 查看每次提交的内容差异
$ git log -p -2 -- <file>
# 参数:-p 表示每次提交的内容差异,-2 则表示显示最近的两次更新。
# 说明:该选项除了显示基本信息之外,还在附带了每次 commit 的变化。

重命名文件或文件夹

$ git mv oldname newname
$ git mv -f oldfolder newfolder
$ git add -u newfolder (-u选项会更新已经追踪的文件和文件夹)
$ git commit -m "changed the foldername whaddup"

清理工作区

去除工作区的修改,清空工作目录中无版本跟踪的文件或目录。

  1. 去除暂存区的文件,标绿的文件(git add的文件),这样暂存区的文件会变为modify

    $ git reset .
    
  2. 去掉modify的文件,相当于全部删除自己的修改

    $ git checkout .
    
  3. 有时候因为冲突导致工作区有好多untrack的文件,想要清空工作区

    # 删除 untracked files
    $ git clean -f
    # 连 untracked 的目录也一起删掉
    $ git clean -fd
    # 连 gitignore 的untrack 文件/目录也一起删掉 (慎用,一般这个是用来删掉编译出来的 .o之类的文件用的)
    $ git clean -xfd
    加上 -n 参数来先看看会删掉哪些文件,防止重要文件被误删
    

永久删除文件和历史记录

有些时候不小心上传了一些敏感文件(例如密码), 或者不想上传的文件没及时或忘了加到.gitignore里的,而且上传的文件又特别大的时候, 这将导致别人clone你的代码或下载zip包的时候也必须更新或下载这些无用的文件,因此, 我们需要一个方法, 永久的删除这些文件(包括该文件的历史记录).

步骤1. 使用git filter-branch删除文件

$ git filter-branch --force --index-filter \
'git rm --cached --ignore-unmatch PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA' \
--prune-empty --tag-name-filter cat -- --all

其中, PATH-TO-YOUR-FILE-WITH-SENSITIVE-DATA 就是你要删除的文件的相对路径(相对于git仓库的根目录), 替换成你要删除的文件路径即可. 注意一点,这里的文件或文件夹,如果以 '/' 开头,则文件或文件夹会被认为是从 git 的安装目录开始。

如果你要删除的目标不是文件,而是文件夹,那么请在 git rm --cached 命令后面添加 -r 命令,表示递归的删除(子)文件夹和文件夹下的文件,类似于 rm -rf 命令。

如果你要删除的文件很多, 可以写进一个.sh文件批量执行, 如果文件或路径里有中文, 由于MinGW或CygWin对中文路径设置比较麻烦, 你可以使用通配符*号, 例如: sound/music_*.mp3, 这样就把sound目录下以music_开头的mp3文件都删除了.

例如:新建一个 bash 脚本文件,del-music-mp3.sh:

#!/bin/bash

git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch projects/Moon.mp3' --prune-empty --tag-name-filter cat -- --all
git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch sound/Music_*.mp3' --prune-empty --tag-name-filter cat -- --all

如果你看到类似下面这样的, 就说明删除成功了:

Rewrite 48dc599c80e20527ed902928085e7861e6b3cbe6 (266/266)
# Ref 'refs/heads/master' was rewritten

如果显示 xxxxx unchanged, 说明repo里没有找到该文件, 请检查路径和文件名是否正确.

如果你想以后也不会再上传这个文件或文件夹, 请把这个文件或文件夹添加到.gitignore文件里, 然后再push你的repo.

步骤2. 推送修改后的repo

以强制覆盖的方式推送你的repo, 命令如下:

$ git push origin master --force --all
Counting objects: 4669, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4352/4352), done.
Writing objects: 100% (4666/4666), 35.16 MiB | 51 KiB/s, done.
Total 4666 (delta 1361), reused 0 (delta 0)
To https://github.com/defunkt/github-gem.git
 + beb839d...81f21f3 master -> master (forced update)

这个过程其实是重新上传我们的repo, 比较耗时, 虽然跟删掉重新建一个repo有些类似, 但是好处是保留了原有的更新记录, 所以还是有些不同的. 如果你实在不在意这些更新记录, 也可以删掉重建, 两者也差不太多, 也许后者还更直观些.

为了能从打了 tag 的版本中也删除你所指定的文件或文件夹,您可以使用这样的命令来强制推送您的 Git tags:

$ git push origin master --force --tags

告诉您的协作者,从您的旧(受污染的)存储库历史中重新创建分支,而不是合并它们。一个合并提交可能会重新引入一些或所有您刚刚陷入清除麻烦的受污染的历史。

步骤3. 清理和回收空间

经过一段时间之后,您确信git filter-branch没有意外的副作用,您可以使用以下命令强制解除对本地存储库中的所有对象的引用和垃圾回收(GC)。

$ git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin
$ git reflog expire --expire=now --all
$ git gc --prune=now
Counting objects: 2437, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (1378/1378), done.
Writing objects: 100% (2437/2437), done.
Total 2437 (delta 1461), reused 1802 (delta 1048)

$ git gc --aggressive --prune=now

Counting objects: 2437, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2426/2426), done.
Writing objects: 100% (2437/2437), done.
Total 2437 (delta 1483), reused 0 (delta 0)

您还可以通过将过滤后的历史推入一个新的或空的存储库,然后从GitHub创建一个新的克隆来实现这一点。

上面命令的第一句也可以换成:

$ rm -rf .git/refs/original/

git filter-branch有很多陷阱,这些陷阱可能会对预期的历史重写产生不明显的破坏(并且由于它的性能如此糟糕,因此您几乎没有时间调查此类问题)。这些安全和性能问题不能向后兼容修复,因此不建议使用它。请使用替代历史过滤工具,例如git filter-repo

参考 github的帮助 remove sensitive data ,说明了一种使用BFG工具的思路,可能更便利。