Qida's Blog

纸上得来终觉浅,绝知此事要躬行。

Git 相比 SVN 的其中一个卓越之处,就在于有各种“后悔药”可吃。其中一种“后悔药”叫做 reset 命令,相当好用。

三个工作区域

理解 reset 命令的前提是理解文件流转的三个工作区域:

  • 工作目录(working directory)
  • 暂存区域(staging area)
  • 本地仓库(repo)

命令

reset 命令有三种参数形式,本文只介绍最常用的一种:

1
2
3
git reset [<mode>] [<commit>]

Reset the current branch head (HEAD) to <commit>, optionally modifying index and working tree to match.

该命令用于回退本地仓库当前分支下的版本,并可以选择重置暂存区域、工作目录的修改。

mode 参数

mode 参数必须是以下五种中的一种:

--soft

HEAD Only

Git 本地仓库的版本回退速度之所以快,全因为 Git 在内部有个指向当前版本的 HEAD 指针,当你回退版本的时候,Git 仅仅是把 HEAD 指针往回移动。

--mixed

HEAD and Index

默认参数。除了回退本地仓库的版本,还会重置暂存区域(也称为 Index File 索引文件)。

这个默默无闻的 --mixed 参数其实很常见,每次运行 git status 时都会看到它的作用:

1
2
3
4
5
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

new file: /there/is/a/new/file
modified: README.md

由于该命令实在太常用了,因此会被设为 alias 以便使用:

1
2
$ git config --global alias.unstage 'reset HEAD'
$ git unstage

--hard

HEAD, Index, and Working Directory

终极武器,将包括工作目录在内的三个工作区域全部重置或回退,工作目录将重置得一干二净,慎用。

常见的做法是回退到上一个版本,连同工作目录,就像一切从未发生过一样:

1
2
3
4
5
$ git reset --hard HEAD~1
HEAD is now at ......

$ git status
nothing to commit, working directory clean

如果“回退前的版本”已经 push 到远程仓库,则不建议这么做。

--merge

待补充。

--keep

待补充。

commit 参数

commit 参数有三种常见形式:

SHA1

使用 SHA1 值回退到指定的版本,适用于 SH1 值已知的情况:

1
$ git reset 17ef24c

更常用的参数,适用于偷懒:

  • HEAD 表示当前版本(默认参数)。
  • 上一个版本为 HEAD^ ,上上一个版本为 HEAD^^ ,以此类推。
  • 上 100 个版本,简写为 HEAD~100
  • ORIG_HEAD 表示上一个 HEAD ,一般用于撤销上一次 reset 。(”reset” copies the old head to .git/ORIG_HEAD)

HEAD@{}

Git 在 1.8.5 版本之后,加入了 HEAD@{} 功能,它通过一个链表记录 HEAD 的移动路径,链表头部的 HEAD@{0}HEAD 指针。这个功能可以用于回退到一个早已忘记的提交。

这个功能一般配合 reflog 命令使用。

例子

更多例子参见 git help reset 的 EXAMPLES 部分。

参考

Git 中 HEAD 和 ORIG_HEAD 指针分别指的是什么?

在提交了若干更新之后,又或者克隆了某个项目,想回顾下提交历史,可以使用 git log 命令查看,或者推荐使用 git 自带的图形化工具 gitk

命令方式

定制输出格式 1

git log 的默认输出格式非常不便于查阅提交历史,使用时可以带上以下三个参数:

1
2
3
4
5
6
7
$ git log --graph --oneline --decorate
* e6f4e18 (HEAD, master) Merge branch 'master' of origin
|\
* | abfa93b 本地仓库的提交
| * 1f1c21d (origin/master) 远程仓库的提交
|/
* 17ef24c 基准版本

命令的输出形象地展示了提交历史,包括本地分支比远程分支领先了多少个提交版本。

定制输出格式 2

如果对输出格式还不满意,可以使用 --pretty 参数定制输出格式:

1
2
3
4
5
6
7
$ git log -5 --pretty=format:"%h - %an, %ar : %s"

f757dcd - wuqd, 20 hours ago : commit msg 5
5ca68df - wuqd, 13 days ago : commit msg 4
486b8d4 - wuqd, 3 weeks ago : commit msg 3
e58ae38 - wuqd, 3 weeks ago : commit msg 2
4830852 - wuqd, 3 weeks ago : commit msg 1

由于该参数的选项较多,推荐设置为别名(alias)使用:

1
2
3
4
5
6
7
8
$ git config --global alias.lg log --graph --pretty=format:'%Cred%h%Creset - %s %Cgreen(%cr) %C(bold blue)<%an>'
$ git lg
* e6f4e18 - Merge branch 'master' of origin (1 minutes ago) <Cyn>
|\
* | abfa93b - 本地仓库的提交 (2 minutes ago) <Pete>
| * 1f1c21d - 远程仓库的提交 (3 minutes ago) <John>
|/
* 17ef24c - 基准版本 (4 minutes ago) <Jerry>

格式化输出,代码着色,而且附上了作者、提交时间和祖先图谱。

求差集

1
2
3
$ git log HEAD --not origin/master
$ git log HEAD ^origin/master
$ git log origin/master..HEAD

也可用于筛选出准备 push 到远程仓库的提交,例如:

1
2
3
4
$ git log --graph --oneline --decorate HEAD --not origin/master
* e6f4e18 (HEAD, master) Merge branch 'master' of origin
|
* abfa93b 本地仓库的提交

图形化方式

如果对输出格式还不满意,推荐使用 gitk 命令调用图形化工具查阅提交历史:

gitk

上半个窗口显示的是历次提交的分支祖先图谱,下半个窗口显示当前点选的提交对应的具体差异(其中右边 Patch 窗口显示当前提交的文件列表,左边 Diff 窗口显示每个文件的提交差异)。

参考

Git 基础 - 查看提交历史

要想 Git 用得爽,首先要安装与配置好。

如何选择版本?

Git 1.x

旧版本,不再维护。

Git 2.x

新版本,推荐使用。不向下兼容 1.x。

如何安装?

Windows

需要安装 Git for Windows 的客户端 msysgit。推荐使用 绿色便携版 ,优势如下:

  • 无需安装,无需写注册表,无需管理员权限;
  • 可以从任意目录运行,甚至 U 盘;

与安装版的区别:

  • 不提供右键上下文菜单(如 Git GUI HereGit Bash Here),因为该功能需要写入注册表;
  • 不修改环境变量 %path% ,因此无法在命令行工具 cmd 中直接运行 git.exegitk.exe,解决办法:
    • 推荐使用自带的 Git Bash (类 Unix Shell)或 Git Cmd 进行替代;
    • 或将 %GIT_HOME%\cmd 目录永久加入环境变量 %path% (如果只想在当前会话中临时使用,只需在 cmd 中运行 set path=%GIT_HOME%\cmd;%path% 即可),然后运行 git --help 测试配置效果;

Linux / Unix

使用包管理 apt-get 或 yum 即可。

OS X

Homebrew 是最快最便捷的安装方式:

1
$ sudo brew install git

如何配置?

Git 相关的配置文件有三个:

  1. /etc/gitconfig 包含了适用于系统所有用户和所有项目的值。
  2. ~/.gitconfig 只适用于当前登录用户的配置。
  3. Git 项目中的 .git/config 适用于特定 Git 项目的配置。

对于同一配置项,三个配置文件的优先级是 3 > 2 >1。

配置提交作者

开始使用 Git 之前,第一件重要的事情就是配置提交作者,配置后就可以愉快的开始使用 Git 了。更多配置请参考 这里

首先做如下检查:

全局配置

使用 --global 查看或修改全局配置文件 ~/.gitconfig

1
$ git config -l | grep user

如果返回为空表示未配置,需要配置如下:

1
2
$ git config --global user.name "你的姓名"
$ git config --global user.email "你的邮箱"

项目配置

如果需要单独修改项目配置文件 .git/config,去掉 --global 参数即可,或者直接打开该文件添加:

1
2
3
[user]
name = who
email = who@where.com

配置格式化与空白

格式化与空白是许多开发人员在协作时,特别是在跨平台情况下,遇到的令人头疼的细小问题。由于编辑器的不同或者Windows程序员在跨平台项目中的文件行尾加入了回车换行符,一些细微的空格变化会不经意地进入大家合作的工作或提交的补丁中。不用怕,Git 的一些配置选项会帮助你解决这些问题。

core.autocrlf

Git 在你提交时自动地把行结束符 CRLF 转换成 LF,而在签出代码时把 LF 转换成 CRLF。假如团队成员只在 Windows 上写程序,可以关闭此功能,避免 Git 自动格式化代码后干扰代码对比:

1
$ git config --global core.autocrlf false

core.whitespace

Git 预先设置了一些选项来探测和修正空白问题,配置方法待补充。

常见问题

Git 长期使用上的一点不便,解决方案仅供参考。

中文乱码显示问题

  1. 打开 GitBash(git-bash.exe)后,对窗口右键->Options->Text->Locale 改为 zh_CN,Character set 改为 GBK ;

  2. 键入exit退出关闭再打开即可。

配置工作目录

Git 默认使用程序运行目录作为工作目录,这会带来使用上的不便。解决办法是新建 .bashrc 文件:

1
$ vim ~/.bashrc

添加一行:

1
2
# 请将 /d/Repos/ 替换成你的仓库目录
cd /d/Repos/

即可自动切换到本地仓库所在目录。

配置 SSH 代理和密钥

另一个潜在的问题是运行 Git Bash 并拉取远程仓库提示错误:

1
Could not open a connection to your authentication agent.

这是因为 ssh-agent 未随 bash 一起启动。你可以每次都手工启动,或推荐编写脚本自启动。

新建 .bashrc 文件,并添加如下代码:

1
2
3
4
5
6
7
8
9
#!/bin/sh
if [ -f ~/.agent.env ]; then
. ~/.agent.env >/dev/null
kill -15 $SSH_AGENT_PID
fi

echo "Starting ssh-agent..."
eval `ssh-agent |tee ~/.agent.env`
ssh-add ~/.ssh/github_rsa

这将会自启动 ssh-agent 并添加指定私钥。 ssh-agent 是一个密钥管理器,运行 ssh-agent 以后,使用 ssh-add 将指定私钥交给 ssh-agent 保管,其它程序(例如 git)在需要身份认证的时候,可以将认证申请交给 ssh-agent 来代为完成整个认证过程。

那么以后每次运行 Git Bash 的时候,就会看到输出效果如下:

1
2
3
Starting ssh-agent...
Agent pid 8828
Identity added: ~/.ssh/github_rsa

参考

之前本博客是挂在 GitHub Pages 空间上的,但由于众所周知的原因访问速度一直很慢,甚至频繁收到无法访问的告警通知,实在是不堪其扰啊。因此决定迁移博客到国内的空间,并且对部分资源进行 CDN 加速。

阅读全文 »

在 JSP 标签中指定一个属性值 value 时,可以使用字符串:

1
<jsp:setProperty name="box" property="perimeter" value="100"/>

也可以使用 EL 表达式:

1
<jsp:setProperty name="box" property="perimeter" value="${expr}"/>

那么,EL 表达式 ${expr} 中可以放些什么呢?

操作符

EL 表达式支持大部分 Java 所提供的算术和逻辑操作符:

基本操作符

操作符 描述
. 访问一个 Bean 的属性或者一个映射条目
[] 访问一个数组或者链表的元素
() 组织一个子表达式以改变优先级

算术操作符

操作符 操作符 描述
+
- 减或负
*
/ div
% mod 取模

逻辑操作符

操作符 操作符 描述
== eq 测试是否相等
!= ne 测试是否不等
< lt 测试是否小于
> gt 测试是否大于
<= le 测试是否小于等于
>= ge 测试是否大于等于
&& and 测试逻辑与
|| or 测试逻辑或
! not 测试取反
empty 测试是否空值

隐式对象

JSP 隐式对象(也称为预定义变量)是 JSP 容器为每个页面提供的 Java 对象,开发者可以直接使用它们而不用显式声明。

作用域

对象 等价物 描述
pageScope this page 作用域
requestScope javax.servlet.http.HttpServletRequest request 作用域
sessionScope javax.servlet.http.HttpSession session 作用域
applicationScope javax.servlet.ServletContext application 作用域

HTTP 请求参数

对象 等价物 描述
param request.getParameter(...) 获取指定 HTTP 请求参数,字符串
paramValues request.getParameterValues() 获取所有 HTTP 请求参数,字符串数组

例如,要判断 HTTP 请求参数 from 是否为空,可以结合使用 JSTL <c:if> 和 EL 操作符 notempty、EL 隐式对象 param 进行判断:

1
<c:if test="${not empty param.from}">

HTTP 请求头

对象 等价物 描述
header request.getHeader(...) 获取指定 HTTP 请求头,字符串
headerValues request.getHeaders() 获取所有 HTTP 请求头,字符串数组

例如,获取请求来源:${header.Referer}

对象 等价物 描述
cookie request.getCookies() javax.servlet.http.Cookie 数组

例如,获取指定 Cookie 的值:${cookie.key.value}。这段 EL 表达式会被 JSP 容器解析成:

1
2
3
4
5
6
7
8
9
10
11
12
Cookie[] cookies = request.getCookies();
Cookie current = null;

for(Cookie cookie : cookies) {
if(cookie.getName().equals("key")) {
current = cookie;
}
}

if(current != null) {
out.print(current.getValue());
}

上下文

对象 等价物 描述
initParam 上下文初始化参数,即 web.xml<context-param>
pageContext javax.servlet.jsp.PageContext 提供对 JSP 页面所有对象以及命名空间的访问

函数

EL 表达式支持使用函数,其使用语法如下:

1
${ns:fn(param1, param2, ...)}

ns 指的是命名空间(namespace),fn 指的是函数的名称,param1 指的是第一个参数,param2 指的是第二个参数,以此类推。

例如,要获取一个字符串的长度,可以使用 JSTL 的 length 函数:

1
${fn:length("Get my length")}

更多 JSTL 函数,参考这里

JSP 标准标签库(JSP Standard Tag Library)是一个 JSP 标签集合,它封装了 JSP 应用的通用核心功能。

它的出现,是因为人们开始注重软件的分层设计,不希望在 JSP 页面中出现 JAVA 逻辑代码。同时也由于自定义标签的开发难度较大、不利于技术的标准化,因此产生了 JSTL。

JSTL 和 EL 的结合,基本可以让页面再无 <% %> 代码。

JSTL 标准标签库可分为五类:

核心标签库

共 14 个,从功能上可以分为 4 类。引用方法:

1
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

表达式控制

标签 描述
<c:out> 用于显示数据,就像 <%= %>,区别在于 <c:out> 标签可以直接通过 . 操作符来访问属性
<c:set> 用于保存数据
<c:remove> 用于删除数据
<c:catch> 用来处理产生错误的异常状况,并且将错误信息储存起来

流程控制

标签 描述
<c:if> 与我们在一般程序中用的 if 一样
<c:choose> 本身只当做 <c:when><c:otherwise> 的父标签
<c:when> <c:choose> 的子标签,用来判断条件是否成立
<c:otherwise> <c:choose> 的子标签,接在 <c:when> 标签后,当 <c:when> 标签判断为 false 时被执行

循环

标签 描述
<c:forEach> 基础迭代标签,接受多种集合类型
<c:forTokens> 根据指定的分隔符来分隔内容并迭代输出

URL 操作

标签 描述
<c:import> 检索一个绝对或相对 URL,然后将其内容暴露给页面
<c:url> 使用可选的查询参数来创造一个 URL
<c:redirect> 重定向至一个新的 URL
<c:param> 用来给包含或重定向的页面传递参数

格式化标签库

用于格式化并输出文本、日期、时间、数字,这里只介绍最最最常用的两个标签。引用方法:

1
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

格式化数字

标签 描述
<fmt:formatNumber> 使用指定的格式或精度格式化数字

格式化日期

标签 描述
<fmt:formatDate> 使用指定的风格或模式格式化日期和时间

SQL 标签库

不常用。引用方法:

1
<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>

XML 标签库

不常用。引用方法:

1
<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>

函数标签库

大部分都是通用的字符串处理函数,用于配合 EL 表达式使用。引用方法:

1
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
标签 描述
fn:contains() 测试输入的字符串是否包含指定的子串
fn:containsIgnoreCase() 测试输入的字符串是否包含指定的子串,大小写不敏感
fn:endsWith() 测试输入的字符串是否以指定的后缀结尾
fn:escapeXml() 跳过可以作为XML标记的字符
fn:indexOf() 返回指定字符串在输入字符串中出现的位置
fn:join() 将数组中的元素合成一个字符串然后输出
fn:length() 返回字符串长度
fn:replace() 将输入字符串中指定的位置替换为指定的字符串然后返回
fn:split() 将字符串用指定的分隔符分隔然后组成一个子字符串数组并返回
fn:startsWith() 测试输入字符串是否以指定的前缀开始
fn:substring() 返回字符串的子集
fn:substringAfter() 返回字符串在指定子串之后的子集
fn:substringBefore() 返回字符串在指定子串之前的子集
fn:toLowerCase() 将字符串中的字符转为小写
fn:toUpperCase() 将字符串中的字符转为大写
fn:trim() 移除首位的空白符

参考

JSP 标准标签库(JSTL)

JSP 有以下三类标签:

JSP Directive

指令标签用于设置与整个 JSP 页面相关的属性,非常常用。

标签 标签 描述
<%@ page ... %> <jsp:directive.page attribute="value" /> 定义页面的依赖属性,例如脚本语言、页面编码、缓存需求等等
<%@ include ... %> <jsp:directive.include file="relative url" /> 引入其它文件,例如 JSP、HTML、文本文件
<%@ taglib ... %> <jsp:directive.taglib uri="uri" prefix="prefixOfTag" /> 引入标签库,可以是 JSP 标准标签库(JSTL)、也可以是自定义标签库

JSP Syntax

语法标签是 Java 早期为了便于开发人员在 JSP 页面中书写业务逻辑而设计的,但目前不再建议使用。

标签 标签 描述
<% scriptlet %> <jsp:scriptlet> scriptlet </jsp:scriptlet> 脚本程序,可以包含任意有效的 Java 语句、变量、方法或表达式
<%! declaration %> <jsp:declaration> declaration </jsp:declaration> 声明语句,可以声明一个或多个变量、方法,供后面的 Java 代码使用
<%= expression %> <jsp:expression> expression </jsp:expression> 表达式,其结果会被转为字符串并输出到 HTML 页面
<%-- comment --%> 代码注释

举个栗子:

1
2
3
4
5
6
7
8
9
<html>
<body>
<%! String output = "world"; %>

<% out.println("Hello " + output); %>
<br/>
<%= "Hello " + output %>
</body>
</html>

渲染输出:

1
2
Hello world 
Hello world

上例中,虽然结合使用这三种语法标签,可以在 JSP 页面中写出大段的 Java 逻辑代码,但强烈不建议这么做,因为这样会导致前端页面和业务逻辑之间紧耦合,以致后续难以维护。

JSP Action

函数标签是一些预定义好的行为标签,但不常用。

标签 描述
<jsp:include> 用于在当前页面中包含静态或动态资源
<jsp:useBean> 寻找和初始化一个 JavaBean 组件
<jsp:setProperty> 设置 JavaBean 组件的值
<jsp:getProperty> 将 JavaBean 组件的值插入到 output 中
<jsp:forward> 从一个 JSP 文件向另一个文件传递一个包含用户请求的 request 对象
<jsp:plugin> 用于在生成的 HTML 页面中包含 Applet 和 JavaBean 对象
<jsp:element> 动态创建一个 XML 元素
<jsp:attribute> 定义动态创建的 XML 元素的属性
<jsp:body> 定义动态创建的 XML 元素的主体
<jsp:text> 用于封装模板数据