Git简单介绍

Git是一个分布式版本控制软件,最初由Linus Torvalds创作,于2005年以GPL发布。最初目的是为更好地管理Linux内核开发而设计。

Git工作流程以及各个区域

  • Workspace:工作区
  • Staging/Index:暂存区
  • Local Repository:本地仓库(可修改)
  • /refs/remotes:远程仓库的引用(不可修改)
  • Remote:远程仓库

Git文件状态变化

Git各种命令

Git简单命令


# 在当前目录新建一个git仓库
git init # 打开git仓库图形界面
gitk # 显示所有变更信息
git status # 删除所有Untracked files
git clean -fd # 下载远程仓库的所有更新
git fetch remote # 下载远程仓库的所有更新,并且Merge
git pull romote branch-name # 查看上次commit id
git rev-parse HEAD # 将指定分支合并到当前分支
git merge branch-name # 将最近的一次commit打包到patch文件中
git format-patch HEAD^ # 将patch文件 添加到本地仓库
git am patch-file # 查看指定文件修改历史
git blame file-name

Git常用命令

git clone

# 将远程git仓库克隆到本地
git clone url # 将远程git仓库克隆到本地
git clone -b branch url

git stash

# 将修改过,未add到Staging区的文件,暂时存储起来
git stash # 恢复之前stash存储的内容
git stash apply # 保存stash 并写message
git stash save "stash test" # 查看stash了哪些存储
git stash list # 将stash@{1}存储的内容还原到工作区
git stash apply stash@{1} # 删除stash@{1}存储的内容
git stash drop stash@{1} # 删除所有缓存的stash
git stash clear

git config

# 配置git图形界面编码为utf-8
git config --global gui.encoding=utf-8 # 设置全局提交代码的用户名
git config --global user.name name
# 设置全局提交代码时的邮箱
git config --global user.email email
# 设置当前项目提交代码的用户名
git config user.name name

git remote

# 显示所有远程仓库
git remote -v # 增加一个新的远程仓库
git remote add name url # 删除指定远程仓库
git remote remove name # 获取指定远程仓库的详细信息
git remote show origin

git add

# 添加所有的修改到Staging区
git add .
git add --all # 添加指定文件到Staging区
git add file # 添加多个修改的文件到Staging区
git add file1 file2 # 添加修改的目录到Staging区
git add dir # 添加所有src目录下main开头的所有文件到Staging区
git add src/main*

git commit

# 提交Staging区的代码到本地仓库区
git commit -m "message" # 提交Staging中在指定文件到本地仓库区
git commit file1 file2 -m "message" # 使用新的一次commit,来覆盖上一次commit
git commit --amend -m "message" # 修改上次提交的用户名和邮箱
git commit --amend --author="name <email>" --no-edit

git branch

# 列出本地所有分支
git branch # 列出本地所有分支 并显示最后一次提交的哈希值
git branch -v # 在-v 的基础上 并且显示上游分支的名字
git branch -vv # 列出上游所有分支
git branch -r # 新建一个分支,但依然停留在当前分支
git branch branch-name # 删除分支
git branch -d branch-name # 设置分支上游
git branch --set-upstream-to origin/master # 本地分支重命名
git branch -m old-branch new-branch

git checkout

# 创建本地分支并关联远程分支
git checkout -b local-branch origin/remote-branch # 新建一个分支,且切换到新分支
git checkout -b branch-name # 切换到另一个分支
git checkout branch-name # 撤销工作区文件的修改,跟上次Commit一样
git checkout commit-file

git tag

# 创建带有说明的标签
git tag -a v1.4 -m 'my version 1.4' # 打标签
git tag tag-name # 查看所有标签
git tag # 给指定commit打标签
git tag tag-name commit-id # 删除标签
git tag -d tag-name

git push

# 删除远程分支
git push origin :master # 删除远程标签
git push origin --delete tag tag-name # 上传本地仓库到远程分支
git push remote branch-name # 强行推送当前分支到远程分支
git push remote branch-name --force # 推送所有分支到远程仓库
git push remote --all # 推送所有标签
git push --tags # 推送指定标签
git push origin tag-name # 删除远程标签(需要先删除本地标签)
git push origin :refs/tags/tag-name # 将本地dev分支push到远程master分支
git push origin dev:master

git reset

# 将未commit的文件移出Staging区
git reset HEAD # 重置Staging区与上次commit的一样
git reset --hard # 重置Commit代码和远程分支代码一样
git reset --hard origin/master # 回退到上个commit
git reset --hard HEAD^ # 回退到前3次提交之前,以此类推,回退到n次提交之前
git reset --hard HEAD~3 回退到指定commit
git reset --hard commit-id

git diff

# 查看文件在工作区和暂存区区别
git diff file-name # 查看暂存区和本地仓库区别
git diff --cached file-name # 查看文件和另一个分支的区别
git diff branch-name file-name # 查看两次提交的区别
git diff commit-id commit-id

git show

# 查看指定标签的提交信息
git show tag-name # 查看具体的某次改动
git show commit-id

git log

# 指定文件夹 log
git log --pretty=format:"%h %cn %s %cd" --author="iisheng\|胜哥" --date=short src
# 查看指定用户指定format 提交
git log --pretty=format:"%h %cn %s %cd" --author=iisheng --date=short # 查看该文件的改动历史
git log --pretty=oneline file # 图形化查看历史提交
git log --graph --pretty=oneline --abbrev-commit # 统计仓库提交排名前5
git log --pretty='%aN' | sort | uniq -c | sort -k1 -n -r | head -n 5 # 查看指定用户添加代码行数,和删除代码行数
git log --author="iisheng" --pretty=tformat: --numstat | awk '{ add += $1 ; subs += $2 } END { printf "added lines: %s removed lines : %s \n",add,subs }'

git rebase

# 将指定分支合并到当前分支
git rebase branch-name # 执行commit id 将rebase 停留在指定commit 处
git rebase -i commit-id # 执行commit id 将rebase 停留在 项目首次commit处
git rebase -i --root

git restore

# 恢复第一次add 的文件,同 git rm --cached
git restore --staged file # 移除staging区的文件,同 git checkout
git restore file

git revert

# 撤销前一次commit
git revert HEAD # 撤销前前一次commit
git revert HEAD^ # 撤销指定某次commit
git revert commit-id

Git骚操作

Git命令不能自动补全?(Mac版)

我见过有的人使用Git别名,反正因为有自动补全的存在,我从来没用过Git别名。不过我的确将我的rm -rf命令替换成了别的脚本了...

安装bash-completion

brew install bash-completion

添加 bash-completion 到 ~/.bash_profile:

 if [ -f $(brew --prefix)/etc/bash_completion ]; then
. $(brew --prefix)/etc/bash_completion
fi

shell有不同种类,我这里使用的是bash

代码没写完,突然要切换到别的分支怎么办?

暂存未提交的代码

git stash

还原暂存的代码

git stash apply

怎么合并其他分支的指定Commit?

使用cherry-pick命令

git cherry-pick 指定commit-id

本地临时代码不想提交,怎么一次性清空?

还原未commit的本地更改的代码

git reset --hard

还原包含commit的代码,到跟远程分支相同

git reset --hard origin/master

已经提交的代码,不需要了,怎么当做没提交过?

还原到上次commit

git reset --hard HEAD^

还原到当前之前的几次commit

git reset --hard HEAD~2

强制推送到远程分支,确保没有其他人在push,不然可能会丢失代码

git push origin develop --force

历史commit作者邮箱写错了,怎么一次性改过来?

使用git filter-branch命令。

复制下面的脚本,替换相关变量

  • OLD_EMAIL
  • CORRECT_NAME
  • CORRECT_EMAIL

脚本如下:

#!/bin/sh

git filter-branch --env-filter '

OLD_EMAIL="your-old-email@example.com"
CORRECT_NAME="Your Correct Name"
CORRECT_EMAIL="your-correct-email@example.com" if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
then
export GIT_COMMITTER_NAME="$CORRECT_NAME"
export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
then
export GIT_AUTHOR_NAME="$CORRECT_NAME"
export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags

强制推送替换

git push --force --tags origin 'refs/heads/*'

不小心把不该提交的文件commit了,怎么永久删除?

也是使用git filter-branch命令。

git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch FILE-PATH-AND-NAME" \
--prune-empty --tag-name-filter cat -- --all

强制推送覆盖远程分支。

git push origin --force --all

强制推送覆盖远程tag

git push origin --force --tags

怎么保证团队成员提交的代码都是可运行的?

这里想说的是使用git hooks,一般在项目目录.git/hooks,客户端可以使用hooks,控制团队commit提交规范,或者push之前,自动编译项目校验项目可运行。服务端可以使用hooks,控制push之后自动构建项目,merge等自动触发单元测试等。

git reset --hard命令,执行错了,能恢复吗?

查看当前commit log

误操作git reset --hard 8529cb7

执行git reflog

还原到之前的样子

公司使用GitLab,平时还用GitHub,多账号SSH,如何配置?

编辑 ~/.ssh/config文件 没有就创建

# github
Host github.com
Port 22
HostName github.com
PreferredAuthentications publickey
AddKeysToAgent yes
IdentityFile ~/.ssh/github_id_rsa
UseKeychain yes
User iisheng # gitlab
Host gitlab.iisheng.cn
Port 22
HostName gitlab.iisheng.cn
PreferredAuthentications publickey
AddKeysToAgent yes
IdentityFile ~/.ssh/gitlab_id_rsa
UseKeychain yes
User iisheng

Git commits历史如何变得清爽起来?

多用git rebase

比如,开发分支是feature,主干分支是master。我们在进行代码合并的时候,可以执行下面的命令。

# 切换当前分支到feature
git checkout feature # 将当前分支代码变基为基于master
git rebase master

然后我们再切换到master分支,执行git merge feature,就可以进行快进式合并了,commmits历史就不会有交叉了。后文我们会详细讲解。

git rebase会更改commit历史,请谨慎使用。

下面的图是Guava项目的commit记录。

如何修改已经提交的commit信息?

原始Git提交记录是这样的

执行git rebase -i 070943d,对指定commitId之前的提交,进行修改

修改后Git提交记录变成了这样

git rebase -i非常实用,还可以将多个commit合并成一个等很多事情,务必要记下。

不小心执行了git stash clear怎么办?

git fsck --lost-found

执行之后,可以找到相关丢失的commit-id,然后merge一下即可。

该命令上可以找回git add之后被弄丢的文件。

啥?你没执行过git add代码就丢了?别怕,一般编译器有Local History赶紧去试试吧。

详解git merge

我们执行git merge命令的时候,经常会看到Fast-forward字样,Fast-forward到底是个什么东西?

其实,git merge一般有三种场景。

快进式合并

举个栗子,假如初始存在masterhotfix分支是这样的。

然后我们在hotfix分支加了些代码,分支变成这样了。

这个时候,我们将hotfix分支,mergemaster,即执行git merge hotfix

由于的分支hotfix所指向的提交C3C2的直接后继, 因此Git会直接将指针向前移动。换句话说,如果顺着一个分支走下去能够到达另一个分支,那么Git在合并两者的时候, 只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 快进(fast-forward)

三方合并

再举个栗子,假如初始存在featuremaster分支情况是这样的。

然后我们在feature分支加了些代码,而master分支也有人加了代码,现在分支变成这样了。

这个时候,我们将feature分支,mergemaster,即执行git merge feature

和之前将分支指针向前推进所不同的是,Git将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。

所以我们也知道了,为什么有的时候merge之后会产生新的commit,而有的时候没有。

遇到冲突时的合并

如果在两个分支分别对同一个文件做了改动,Git就没法直接合并他们。当遇到冲突的时候,Git会自动停下来,等待我们解决冲突。就像这样

$ git merge dev
Auto-merging 111.txt
CONFLICT (content): Merge conflict in 111.txt
Automatic merge failed; fix conflicts and then commit the result.

我们可以在合并冲突后的任意时刻使用git status命令来查看那些因包含合并冲突而处于未合并unmerged状态的文件。

$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge) Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: 111.txt no changes added to commit (use "git add" and/or "git commit -a")

待解决冲突的文件Git会以未合并的状态标识出来,出现冲突的文件会出现一些特殊的区段,看起来像下面的样子。

<<<<<<< HEAD
111aaa
=======
111b
>>>>>>> dev

<<<<<<< 后面的是当前分支的引用,我们的例子中,就代表master分支。>>>>>>>后面表示的是要合并到当前分支的分支,即dev分支。=======的上半部分,表示当前分支的代码。下半部分表示dev分支的代码。

我们可以把上面的测试内容改成下面的样子来解决冲突

111aaa

在解决了所有文件里的冲突之后,对每个文件使用git add命令来将其标记为冲突已解决。

解决冲突的过程中,每一步都可以执行git status查看当前状态,Git也会给出相应提示,进行下一步操作。当我们所有的文件都暂存之后时,执行git status时,Git会给我们看起来像下面的这种提示

$ git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)

然后,我们根据提示执行git commit

Merge branch 'dev'

# Conflicts:
# 111.txt
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
# .git/MERGE_HEAD
# and try again. # Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# All conflicts fixed but you are still merging.
#

然后,我们保存这次提交就完成了这次冲突合并。

详解git rebase

rebase做了什么

举个栗子。我们同样用刚才merge的场景。

如果不用rebase,使用merge是下面这样的,合并分支的时候会产生一个合并提交,而且会有分支交叉的情况。

使用rebase是下面这样的。

然后,切换到master分支,进行一次快进式合并。

变基实际上就是基于其他分支重塑当前分支。变基之后,当前分支就相当于是基于最新的其他分支新加了一些commit,这样的话就可以进行快进式合并了。

rebase原理

它的原理是首先找到这两个分支(即当前分支 dev、变基操作的目标基底分支master)的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件, 然后将当前分支指向目标基底C3, 最后以此将之前另存为临时文件的修改依序应用,也就是在C3后面添加C4'C5'

Git对象与快照

提到Git,总有人会说快照快照是个什么鬼?

实际上,Git是一个内容寻址文件系统,其核心部分是一个简单的键值对数据库。将Git中的对象,存储在.git/objects目录下。

Git对象主要分为,数据对象(blob object)树对象(tree object)提交对象(commit object)标签对象(tag object)

数据对象

我们新建一个目录,然后在该目录下执行git init初始化一个Git项目。

然后,查看.git/objects目录下都有什么。

$ find .git/objects
.git/objects
.git/objects/pack
.git/objects/info

接着,我们写一个文件echo '1111' > 111.txt,并执行git add之后,再查看。

$ find .git/objects
.git/objects
.git/objects/5f
.git/objects/5f/2f16bfff90e6620509c0cf442e7a3586dad8fb
.git/objects/pack
.git/objects/info

我们发现.git/objects目录下,多了个文件和目录。实际上,Git会将我们的文件数据外加一个头部信息header一起做SHA-1校验运算而得到校验和。然后,校验和的前2个字符用于命名子目录,余下的38个字符则用作文件名。

我们可以使用下面的命令,显示在Git对象中存储的内容。

$ git cat-file -p 5f2f16bfff90e6620509c0cf442e7a3586dad8fb
1111

这就是我们在上文写入的文件内容。

上述类型的对象称之为数据对象(blob object)。数据对象,仅保存了文件内容,而文件名字没有被保存。

树对象

数据对象大致对应UNIX中的inodes或文件内容,树对象则对应了UNIX中的目录项。一个树对象包含了一条或多条树对象记录(tree entry),每条记录含有一个指向数据对象或者子树对象的SHA-1指针,以及相应的模式、类型、文件名信息。

通常,Git根据某一时刻暂存区(即index区域)所表示的状态创建并记录一个对应的树对象。

当我们执行过git add之后,暂存区就有内容了,我们可以通过Git底层命令,生成树对象。

$ git write-tree
b716c7b049ccd9048b0566a57cfd516c17c1e39f

查看该树对象的内容。

$ git cat-file -p b716c7b049ccd9048b0566a57cfd516c17c1e39f
100644 blob 5f2f16bfff90e6620509c0cf442e7a3586dad8fb 111.txt

提交对象

数据对象保存了数据的内容,树对象可以表示当前目录的快照。但是,若想重用这些快照,必须记住树对象的SHA-1哈希值。而且,我们也不知道是谁保存了这些快照,在什么时刻保存的,以及为什么保存这些快照。而以上这些,正是

提交对象(commit object)

能保存的基本信息。

我们对当前暂存区进行一次提交,git commit -m "first commit"

然后查看一下log找到该次提交的commit哈希值。

$ git log --oneline
5281f7e (HEAD -> master) first commit

接着,我们查看一下该提交对象的内容。

$ git cat-file -p 5281f7e
tree b716c7b049ccd9048b0566a57cfd516c17c1e39f
author iisheng <***@gmail.com> 1596073568 +0800
committer iisheng <***@gmail.com> 1596073568 +0800 first commit

提交对象的格式很简单:它先指定一个顶层树对象,代表当前项目快照;然后是可能存在的父提交(前面描述的提交对象并不存在任何父提交);之后是作者/提交者信息(依据你的user.nameuser.email配置来设定,外加一个时间戳);留空一行,最后是提交注释。

标签对象

标签对象(tag object) 非常类似于一个提交对象——它包含一个标签创建者信息、一个日期、一段注释信息,以及一个指针。主要的区别在于,标签对象通常指向一个提交对象,而不是一个树对象。它像是一个永不移动的分支引用——永远指向同一个提交对象,只不过给这个提交对象加上一个更友好的名字罢了。

实际上Git中的各种对象都是类似的,只不过因为各种对象自身功能不同,存储结构不同而已。

Git引用-我从远程拉的代码不是最新的?

Git引用相当于是Git中特定哈希值的别名。一长串的哈希值不是很友好,但是起个别名,我们就可以像这样git show mastergit log master的去使用他们。

Git中的引用存储在.git/refs目录下。我们可以执行find .git/refs/查看当前Git项目中都存在哪些引用。

HEAD引用

.git目录下有一个名字叫做HEAD的文件,HEAD文件通常是一个符号引用(symbolic reference)指向目前所在的分支。所谓符号引用,表示它是一个指向其他引用的指针。

如果我们在工作区checkout一个SHA-1值,HEAD引用也会指向这个包含Git对象的SHA-1值。

标签引用

Git标签分为,附注标签和轻量标签。轻量标签,使用 git tag v1.0即可创建。附注标签需要使用-a选项,即git tag -a v1.0 -m "my version 1.0"这种。

轻量标签就是一个固定的引用。附注标签需要创建标签对象,并记录一个引用来指向该标签对象。

远程引用

不熟悉Git的同学,可能会犯这样一个错误。其他同学让他拉取一下远程最新的master分支代码,他可能直接用IDE找到本地的远程分支的引用,也就是origin/master,直接checkout一个本地分支。

其实,origin/master只是远程分支的一个引用,不一定跟远程分支代码同步,我们可以用git fetch或者git pull来让origin/master和远程分支同步。

参考文献:


[1]: https://git-scm.com/

欢迎关注个人微信公众号【如逆水行舟】,用心输出基础、算法、源码系列文章。

Git科普文,Git基本原理&各种骚操作的更多相关文章

  1. Git使用文档

    建立项目 新建项目 进入gitlab.dev(192.168.14.28) 选择LDAP,用自己的域账号登录 点击右上角的 加号(+)新建项目 填写项目名称 选择组为 Online_Web “Visi ...

  2. GIT使用教程与基本原理

    转自:http://blog.csdn.net/wengpingbo/article/details/8985132 说明:该教程全部图片都来自于<pro git>.以下所有的操作,除非特 ...

  3. GIt帮助文档之忽略某些文件——忽略python虚拟环境文件夹(转)

    前言:为避免多个Python项目下安装库之间的冲突,或为轻松打包某个项目,建议在每个项目文件夹下安装Python虚拟环境,并在虚拟环境内进行操作,之后你安装的任何库和执行的任何程序都是在这个环境下运行 ...

  4. [转]git图解(3):分支操作

    本文转自:https://www.jianshu.com/p/342a9f8db004   title_img.png git 的分支是它最明显的特性, 大部分人听别人推荐使用git都会听到“git分 ...

  5. Git入门到高级系列2-git高级操作

    视频课程地址 腾讯课堂 git 清理 git clean命令用来从你的工作目录中删除所有没有tracked过的文件. 命令 说明 git clean -n 告诉你哪些文件会被删除. 记住他不会真正的删 ...

  6. Git 学习(三)本地仓库操作——git add & commit

    Git 学习(三)本地仓库操作——git add & commit Git 和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念.这在上文已有提及,本文具体说明什么是工作区及暂存区,以及 ...

  7. git 学习记录—— git 中的仓库、文件状态、修改和提交操作等

    最近开始学习使用版本控制工具  git .学习方式主要通过阅读 git 网站上的 Pro git 和动手实践,使用的系统为 Ubuntu16.04LTS,以及 Windows 8.1. 本文主要关注 ...

  8. delphi xe 10.3 利用Git组群开发,Git服务器安装,Git 拉取,提交,推送相关设置操作

    1. Git服务器安装, 参考 https://blog.csdn.net/u012842630/article/details/97175397 Git服务器官方网站,要FQ. 2. 工具软件 gi ...

  9. 合并代码操作 | git fetch 与 git pull

    前言 首先我们要说简单说git的运行机制.git分为本地仓库和远程仓库,我们一般情况都是写完代码,commit到本地仓库(生成本地仓的commit ID,代表当前提交代码的版本号),然后push到远程 ...

随机推荐

  1. AHP(使用于某项目设备重要度评估测试)

    用层次法和蒙特卡洛模型计算权重系数,然后建立判断矩阵进行随机一致性检验,最后求出重要度指数. string calculateStr = "1,2,3,2,1,|1,2,3,2,1,|1,2 ...

  2. 机器学习实战基础(十九):sklearn中数据集

    sklearn提供的自带的数据集   sklearn 的数据集有好多个种 自带的小数据集(packaged dataset):sklearn.datasets.load_<name> 可在 ...

  3. 《串并行数据结构与算法(SML语言)实验》题解

    注意:本题解仅供参考学习,请勿直接抄袭代码,否则造成的后果和笔者无关. 第一题: 题意: 对n个数升序排序. 题解: 快排,不解释. 代码(省略了输入输出函数,下同): val n = getInt ...

  4. 【Nginx】如何为已安装的Nginx动态添加模块?看完我懂了!!

    写在前面 很多时候,我们根据当时的项目情况和业务需求安装完Nginx后,后续随着业务的发展,往往会给安装好的Nginx添加其他的功能模块.在为Nginx添加功能模块时,要求Nginx不停机.这就涉及到 ...

  5. Windows下配置ChromeDriver

    1.查看自己chrome浏览器的版本. 浏览器地址栏输入以下地址 chrome://version 2.通过自己的版本下载相应的chromedriver.exe 下载地址:http://npm.tao ...

  6. SpingBoot整合jxls2.0-excel导出—— 列表循环,自定义方法,超链接等

    Java中实现excel导出数据的方法有很多,一般简单的可以通过操作POI进行,但是复杂的excel格式导出如果用POI就显得非常麻烦,本文介绍的jxls2.0完全依据模板进行导出,只需要进行简单的配 ...

  7. java 两个数组相减结果

    public static void main(String[] args) { String[] a = new String[] { "1", "5", & ...

  8. Ethical Hacking - NETWORK PENETRATION TESTING(9)

    WEP Cracking Packet Injection What if the AP was idle, or had no clients associated with it? In this ...

  9. 围绕一个 volatile 关键字居然可以问出来 16 个问题

    对于 Java 每次面试就会想到多线程,多线程问题基本跑不了要问一下 volalite 关键字,可是我万万没想到居然一个 volatile 关键字可以连续问题出来 16 个问题!看下你能回答出来几个? ...

  10. layui 魔改:上传时的真实进度条

    这个问题本身不复杂,难点在于需要改 layui 的源码. HTML略. 网页的JS域: layui.use(['upload','element','layer'], function(){ var ...