Devil May Code...

Vergil's Blog

How To Ask Questions The Smart Way

Copyright © 2001,2006,2014 Eric S. Raymond, Rick Moen

本指南英文版版权为 Eric S. Raymond, Rick Moen 所有。

原文网址:http://www.catb.org/~esr/faqs/smart-questions.html

Copyleft 2001 by D.H.Grand(nOBODY/Ginux), 2010 by Gasolin, 2015 by Ryan Wu

本中文指南是基于原文 3.10 版以及 2010 年由 Gasolin 所翻译版本的最新翻译;

协助指出翻译问题,发Issue,或直接发Pull Request给我。

原文版本历史

目录

阅读剩余部分...


转自:http://www.ha97.com/4617.html


PS:网站性能压力测试是性能调优过程中必不可少的一环。只有让服务器处在高压情况下才能真正体现出各种设置所暴露的问题。Apache中有个自带的,名为ab的程序,可以对Apache或其它类型的服务器进行网站访问压力测试。

ApacheBench命令原理:

ab命令会创建很多的并发访问线程,模拟多个访问者同时对某一URL地址进行访问。它的测试目标是基于URL的,因此,既可以用来测试Apache的负载压力,也可以测试nginx、lighthttp、tomcat、IIS等其它Web服务器的压力。

ab命令对发出负载的计算机要求很低,既不会占用很高CPU,也不会占用很多内存,但却会给目标服务器造成巨大的负载,其原理类似CC攻击。自己测试使用也须注意,否则一次上太多的负载,可能造成目标服务器因资源耗完,严重时甚至导致死机。

ApacheBench参数说明

格式:ab [options] [http://]hostname[:port]/path
参数说明:
-n requests Number of requests to perform
//在测试会话中所执行的请求个数(本次测试总共要访问页面的次数)。默认时,仅执行一个请求。
-c concurrency Number of multiple requests to make
//一次产生的请求个数(并发数)。默认是一次一个。
-t timelimit Seconds to max. wait for responses
//测试所进行的最大秒数。其内部隐含值是-n 50000。它可以使对服务器的测试限制在一个固定的总时间以内。默认时,没有时间限制。
-p postfile File containing data to POST
//包含了需要POST的数据的文件,文件格式如“p1=1&p2=2”.使用方法是 -p 111.txt 。 (配合-T)
-T content-type Content-type header for POSTing
//POST数据所使用的Content-type头信息,如 -T “application/x-www-form-urlencoded” 。 (配合-p)
-v verbosity How much troubleshooting info to print
//设置显示信息的详细程度 – 4或更大值会显示头信息, 3或更大值可以显示响应代码(404, 200等), 2或更大值可以显示警告和其他信息。 -V 显示版本号并退出。
-w Print out results in HTML tables
//以HTML表的格式输出结果。默认时,它是白色背景的两列宽度的一张表。
-i Use HEAD instead of GET
// 执行HEAD请求,而不是GET。
-x attributes String to insert as table attributes
-y attributes String to insert as tr attributes
-z attributes String to insert as td or th attributes
-C attribute Add cookie, eg. -C “c1=1234,c2=2,c3=3″ (repeatable)
//-C cookie-name=value 对请求附加一个Cookie:行。 其典型形式是name=value的一个参数对。此参数可以重复,用逗号分割。
提示:可以借助session实现原理传递 JSESSIONID参数, 实现保持会话的功能,如
-C ” c1=1234,c2=2,c3=3, JSESSIONID=FF056CD16DA9D71CB131C1D56F0319F8″ 。
-H attribute Add Arbitrary header line, eg. ‘Accept-Encoding: gzip’ Inserted after all normal header lines. (repeatable)
-A attribute Add Basic WWW Authentication, the attributes
are a colon separated username and password.
-P attribute Add Basic Proxy Authentication, the attributes
are a colon separated username and password.
//-P proxy-auth-username:password 对一个中转代理提供BASIC认证信任。用户名和密码由一个:隔开,并以base64编码形式发送。无论服务器是否需要(即, 是否发送了401认证需求代码),此字符串都会被发送。
-X proxy:port Proxyserver and port number to use
-V Print version number and exit
-k Use HTTP KeepAlive feature
-d Do not show percentiles served table.
-S Do not show confidence estimators and warnings.
-g filename Output collected data to gnuplot format file.
-e filename Output CSV file with percentages served
-h Display usage information (this message)
//-attributes 设置属性的字符串. 缺陷程序中有各种静态声明的固定长度的缓冲区。另外,对命令行参数、服务器的响应头和其他外部输入的解析也很简单,这可能会有不良后果。它没有完整地实现 HTTP/1.x; 仅接受某些’预想’的响应格式。 strstr(3)的频繁使用可能会带来性能问题,即你可能是在测试ab而不是服务器的性能。

参数很多,一般我们用 -c 和 -n 参数就可以了。例如:

# ab -c 5000 -n 600 http://127.0.0.1/index.php

ApacheBench用法详解:

在Linux系统,一般安装好Apache后可以直接执行;

# ab -n 4000 -c 1000 http://www.ha97.com/

如果是Win系统下,打开cmd命令行窗口,cd到apache安装目录的bin目录下;

-n后面的4000代表总共发出4000个请求;-c后面的1000表示采用1000个并发(模拟1000个人同时访问),后面的网址表示测试的目标URL。

稍等一会得到类似如下显示结果:

结果分析:

This is ApacheBench, Version 2.3
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking 192.168.80.157 (be patient)
Completed 400 requests
Completed 800 requests
Completed 1200 requests
Completed 1600 requests
Completed 2000 requests
Completed 2400 requests
Completed 2800 requests
Completed 3200 requests
Completed 3600 requests
Completed 4000 requests
Finished 4000 requests

Server Software: Apache/2.2.15
Server Hostname: 192.168.80.157
Server Port: 80

Document Path: /phpinfo.php
#测试的页面
Document Length: 50797 bytes
#页面大小

Concurrency Level: 1000
#测试的并发数
Time taken for tests: 11.846 seconds
#整个测试持续的时间
Complete requests: 4000
#完成的请求数量
Failed requests: 0
#失败的请求数量
Write errors: 0
Total transferred: 204586997 bytes
#整个过程中的网络传输量
HTML transferred: 203479961 bytes
#整个过程中的HTML内容传输量
Requests per second: 337.67 [#/sec] (mean)
#最重要的指标之一,相当于LR中的每秒事务数,后面括号中的mean表示这是一个平均值
Time per request: 2961.449 [ms] (mean)
#最重要的指标之二,相当于LR中的平均事务响应时间,后面括号中的mean表示这是一个平均值
Time per request: 2.961 [ms] (mean, across all concurrent requests)
#每个连接请求实际运行时间的平均值
Transfer rate: 16866.07 [Kbytes/sec] received
#平均每秒网络上的流量,可以帮助排除是否存在网络流量过大导致响应时间延长的问题
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 483 1773.5 11 9052
Processing: 2 556 1459.1 255 11763
Waiting: 1 515 1459.8 220 11756
Total: 139 1039 2296.6 275 11843
#网络上消耗的时间的分解,各项数据的具体算法还不是很清楚

Percentage of the requests served within a certain time (ms)
50% 275
66% 298
75% 328
80% 373
90% 3260
95% 9075
98% 9267
99% 11713
100% 11843 (longest request)
#整个场景中所有请求的响应情况。在场景中每个请求都有一个响应时间,其中50%的用户响应时间小于275毫秒,66%的用户响应时间小于298毫秒,最大的响应时间小于11843毫秒。对于并发请求,cpu实际上并不是同时处理的,而是按照每个请求获得的时间片逐个轮转处理的,所以基本上第一个Time per request时间约等于第二个Time per request时间乘以并发请求数。

总结:在远程对web服务器进行压力测试,往往效果不理想(因为网络延时过大),建议使用内网的另一台或者多台服务器通过内网进行测试,这样得出的数据,准确度会高很多。如果只有单独的一台服务器,可以直接本地测试,比远程测试效果要准确。


转自阮一峰的网络日志


我每天使用 Git ,但是很多命令记不住。
一般来说,日常使用只要记住下图6个命令,就可以了。但是熟练使用,恐怕要记住60~100个命令。

下面是我整理的常用 Git 命令清单。几个专用名词的译名如下。
Workspace:工作区
Index / Stage:暂存区
Repository:仓库区(或本地仓库)
Remote:远程仓库

阅读剩余部分...


  • 文档主要目的是为设计接口时提供建议,使大家不必重复造 HTTP 协议已经完成的轮子
  • 只是建议,不是必须遵从的要求
  • 大家有什么问题想法或者建议欢迎 创建 Issue 或者 提交 Pull Request

目录

HTTP 协议

HTTP/1.1

2014 年 6 月的时候 IETF 已经正式的废弃了 RFC 2616 ,将它拆分为六个单独的协议说明,并重点对原来语义模糊的部分进行了解释:

相关资料:

HTTP/2

HTTP 协议的 2.0 版本还没有正式发布,但目前已经基本稳定下来了。

2.0 版本的设计目标是尽量在使用层面上保持与 1.1 版本的兼容,所以,虽然数据交换的格式发生了变化,但语义基本全部被保留下来了

因此,作为使用者而言,我们并不需要为了支持 2.0 而大幅修改代码。

URL

HOST 地址:

https://api.example.com

所有 URI 都需要遵循 RFC 3986 的要求。

强烈建议 API 部署 SSL 证书,这样接口传递的数据的安全性才能都得一定的保障。

空字段

接口遵循“输入宽容,输出严格”原则,输出的数据结构中空字段的值一律为 null

国际化

语言标签

RFC 5646 (BCP 47) 规定的语言标签的格式如下:

language-script-region-variant-extension-privateuse
  1. language:这部分使用的是 ISO 639-1, ISO 639-2, ISO 639-3, ISO 639-5 中定义的语言代码,必填
    • 这个部分由 primary-extlang 两个部分构成
    • primary 部分使用 ISO 639-1, ISO 639-2, ISO 639-3, ISO 639-5 中定义的语言代码,优先使用 ISO 639-1 中定义的条目,比如汉语 zh
    • extlang 部分是在某些历史性的兼容性的原因,在需要非常细致地区别 primary 语言的时候使用,使用 ISO 639-3 中定义的三个字母的代码,比如普通话 cmn
    • 虽然 language 可以只写 extlang 省略 primary 部分,但出于兼容性的考虑,还是建议加上 primary 部分
  2. script: 这部分使用的是 ISO 15924 (Wikipedia) 中定义的语言代码,比如简体汉字是 zh-Hans ,繁体汉字是 zh-Hant
  3. region: 这部分使用的是 ISO 3166-1 (Wikipedia) 中定义的地理区域代码,比如 zh-Hans-CN 就是中国大陆使用的简体中文。
  4. variant: 用来表示 extlang 的定义里没有包含的方言,具体的使用方法可以参考 RFC 5646
  5. extension: 用来为自己的应用做一些语言上的额外的扩展,具体的使用方法可以参考 RFC 5646
  6. privateuse: 用来表示私有协议中约定的一些语言上的区别,具体的使用方法可以参考 RFC 5646

其中只有 language 部分是必须的,其他部分都是可选的;不过为了便于编写程序,建议设计接口时约定语言标签的结构,比如统一使用 language-script-region 的形式( zh-Hans-CN, zh-Hant-HK 等等)。

语言标签是大小写不敏感的,但按照惯例,建议 script 部分首字母大写, region 部分全部大写,其余部分全部小写。

有一点需要注意,任何合法的标签都必须经过 IANA 的认证,已通过认证的标签可以在这个网页查到。此外,网上还有一个非官方的标签搜索引擎

相关资料:

时区

客户端请求服务器时,如果对时间有特殊要求(如某段时间每天的统计信息),则可以参考 IETF 相关草案 增加请求头 Timezone

Timezone: 2007-06-12T23:48:22+0800
// OR
Timezone: 1977-07-30T12:00:11+0200;;Europe/Athens

时区的名称可以参考 tz datebase(Wikipedia) 。

如果客户端请求时没有指定相应的时区,则服务端默认使用 UTC 时间返回相应数据。

PS 考虑到存在夏时制这种东西,所以不推荐客户端在请求时使用 Offset 。

时间格式

时间格式遵循 ISO 8601(Wikipedia) 建议的格式:

  • 日期 2014-07-09
  • 时间 14:31:22+0800
  • 具体时间 2007-11-06T16:34:41Z
  • 持续时间 P1Y3M5DT6H7M30S (表示在一年三个月五天六小时七分三十秒内)
  • 时间区间 2007-03-01T13:00:00Z/2008-05-11T15:30:00Z2007-03-01T13:00:00Z/P1Y2M10DT2H30MP1Y2M10DT2H30M/2008-05-11T15:30:00Z
  • 重复时间 R3/2004-05-06T13:00:00+08/P0Y6M5DT3H0M0S (表示从2004年5月6日北京时间下午1点起,在半年零5天3小时内,重复3次)

相关资料:

货币名称

货币名称可以参考 ISO 4217(Wikipedia) 中的约定,标准为货币名称规定了三个字母的货币代码,其中的前两个字母是 ISO 3166-1(Wikipedia) 中定义的双字母国家代码,第三个字母通常是货币的首字母。在货币上使用这些代码消除了货币名称(比如 dollar )或符号(比如 $ )的歧义。

相关资料:

  • 《RESTful Web Services Cookbook 中文版》 3.9 节《如何在表述中使用可移植的数据格式》

请求方法

  • 如果请求头中存在 X-HTTP-Method-Override 或参数中存在 _method(拥有更高权重),且值为 GET, POST, PUT, DELETE, PATCH, OPTION, HEAD 之一,则视作相应的请求方式进行处理
  • GET, DELETE, HEAD 方法,参数风格为标准的 GET 风格的参数,如 url?a=1&b=2
  • POST, PUT, PATCH, OPTION 方法
    • 默认情况下请求实体会被视作标准 json 字符串进行处理,当然,依旧推荐设置头信息的 Content-Typeapplication/json
    • 在一些特殊接口中(会在文档中说明),可能允许 Content-Typeapplication/x-www-form-urlencoded 或者 multipart/form-data ,此时请求实体会被视作标准 POST 风格的参数进行处理

关于方法语义的说明:

  • OPTIONS 用于获取资源支持的所有 HTTP 方法
  • HEAD 用于只获取请求某个资源返回的头信息
  • GET 用于从服务器获取某个资源的信息
    • 完成请求后返回状态码 200 OK
    • 完成请求后需要返回被请求的资源详细信息
  • POST 用于创建新资源
    • 创建完成后返回状态码 201 Created
    • 完成请求后需要返回被创建的资源详细信息
  • PUT 用于完整的替换资源或者创建指定身份的资源,比如创建 id 为 123 的某个资源
    • 如果是创建了资源,则返回 201 Created
    • 如果是替换了资源,则返回 200 OK
    • 完成请求后需要返回被修改的资源详细信息
  • PATCH 用于局部更新资源
    • 完成请求后返回状态码 200 OK
    • 完成请求后需要返回被修改的资源详细信息
  • DELETE 用于删除某个资源
    • 完成请求后返回状态码 204 No Content

相关资料:

状态码

请求成功

  • 200 OK : 请求执行成功并返回相应数据,如 GET 成功
  • 201 Created : 对象创建成功并返回相应资源数据,如 POST 成功;创建完成后响应头中应该携带头标 Location ,指向新建资源的地址
  • 202 Accepted : 接受请求,但无法立即完成创建行为,比如其中涉及到一个需要花费若干小时才能完成的任务。返回的实体中应该包含当前状态的信息,以及指向处理状态监视器或状态预测的指针,以便客户端能够获取最新状态。
  • 204 No Content : 请求执行成功,不返回相应资源数据,如 PATCHDELETE 成功

重定向

重定向的新地址都需要在响应头 Location 中返回

  • 301 Moved Permanently : 被请求的资源已永久移动到新位置
  • 302 Found : 请求的资源现在临时从不同的 URI 响应请求
  • 303 See Other : 对应当前请求的响应可以在另一个 URI 上被找到,客户端应该使用 GET 方法进行请求
  • 307 Temporary Redirect : 对应当前请求的响应可以在另一个 URI 上被找到,客户端应该保持原有的请求方法进行请求

条件请求

  • 304 Not Modified : 资源自从上次请求后没有再次发生变化,主要使用场景在于实现数据缓存
  • 409 Conflict : 请求操作和资源的当前状态存在冲突。主要使用场景在于实现并发控制
  • 412 Precondition Failed : 服务器在验证在请求的头字段中给出先决条件时,没能满足其中的一个或多个。主要使用场景在于实现并发控制

客户端错误

  • 400 Bad Request : 请求体包含语法错误
  • 401 Unauthorized : 需要验证用户身份,如果服务器就算是身份验证后也不允许客户访问资源,应该响应 403 Forbidden
  • 403 Forbidden : 服务器拒绝执行
  • 404 Not Found : 找不到目标资源
  • 405 Method Not Allowed : 不允许执行目标方法,响应中应该带有 Allow 头,内容为对该资源有效的 HTTP 方法
  • 406 Not Acceptable : 服务器不支持客户端请求的内容格式,但响应里会包含服务端能够给出的格式的数据,并在 Content-Type 中声明格式名称
  • 410 Gone : 被请求的资源已被删除,只有在确定了这种情况是永久性的时候才可以使用,否则建议使用 404 Not Found
  • 413 Payload Too Large : POST 或者 PUT 请求的消息实体过大
  • 415 Unsupported Media Type : 服务器不支持请求中提交的数据的格式
  • 422 Unprocessable Entity : 请求格式正确,但是由于含有语义错误,无法响应
  • 428 Precondition Required : 要求先决条件,如果想要请求能成功必须满足一些预设的条件

服务端错误

  • 500 Internal Server Error : 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。
  • 501 Not Implemented : 服务器不支持当前请求所需要的某个功能。
  • 502 Bad Gateway : 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
  • 503 Service Unavailable : 由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,并且将在一段时间以后恢复。如果能够预计延迟时间,那么响应中可以包含一个 Retry-After 头用以标明这个延迟时间(内容可以为数字,单位为秒;或者是一个 HTTP 协议指定的时间格式)。如果没有给出这个 Retry-After 信息,那么客户端应当以处理 500 响应的方式处理它。

501405 的区别是:405 是表示服务端不允许客户端这么做,501 是表示客户端或许可以这么做,但服务端还没有实现这个功能

相关资料:

错误处理

在调用接口的过程中,可能出现下列几种错误情况:

  • 服务器维护中,503 状态码

    HTTP/1.1 503 Service Unavailable
    Retry-After: 3600
    Content-Length: 41
    
    {"message": "Service In the maintenance"}
    
  • 发送了无法转化的请求体,400 状态码

    HTTP/1.1 400 Bad Request
    Content-Length: 35
    
    {"message": "Problems parsing JSON"}
    
  • 服务到期(比如付费的增值服务等), 403 状态码

    HTTP/1.1 403 Forbidden
    Content-Length: 29
    
    {"message": "Service expired"}
    
  • 因为某些原因不允许访问(比如被 ban ),403 状态码

    HTTP/1.1 403 Forbidden
    Content-Length: 29
    
    {"message": "Account blocked"}
    
  • 权限不够,403 状态码

    HTTP/1.1 403 Forbidden
    Content-Length: 31
    
    {"message": "Permission denied"}
    
  • 需要修改的资源不存在, 404 状态码

    HTTP/1.1 404 Not Found
    Content-Length: 32
    
    {"message": "Resource not found"}
    
  • 缺少了必要的头信息,428 状态码

    HTTP/1.1 428 Precondition Required
    Content-Length: 35
    
    {"message": "Header User-Agent is required"}
    
  • 发送了非法的资源,422 状态码

    HTTP/1.1 422 Unprocessable Entity
    Content-Length: 149
    
    {
      "message": "Validation Failed",
      "errors": [
        {
          "resource": "Issue",
          "field": "title",
          "code": "required"
        }
      ]
    }
    

所有的 error 哈希表都有 resource, field, code 字段,以便于定位错误,code 字段则用于表示错误类型:

  • invalid: 某个字段的值非法,接口文档中会提供相应的信息
  • required: 缺失某个必须的字段
  • not_exist: 说明某个字段的值代表的资源不存在
  • already_exist: 发送的资源中的某个字段的值和服务器中已有的某个资源冲突,常见于某些值全局唯一的字段,比如 @ 用的用户名(这个错误我有纠结,因为其实有 409 状态码可以表示,但是在修改某个资源时,很一般显然请求中不止是一种错误,如果是 409 的话,多种错误的场景就不合适了)

身份验证

部分接口需要通过某种身份验证方式才能请求成功(这些接口应该在文档中标注出来),合适的身份验证解决方案目前有两种:

超文本驱动和资源发现

REST 服务的要求之一就是超文本驱动,客户端不再需要将某些接口的 URI 硬编码在代码中,唯一需要存储的只是 API 的 HOST 地址,能够非常有效的降低客户端与服务端之间的耦合,服务端对 URI 的任何改动都不会影响到客户端的稳定。

目前有几种方案试图实现这个效果:

目前所知的方案都实现了发现资源的功能,服务端同时需要实现 OPTIONS 方法,并在响应中携带 Allow 头来告知客户端当前拥有的操作权限。

分页

请求某个资源集合时,可以通过指定 count 参数来指定每页的资源数量,通过 page 参数指定页码,或根据 last_cursor 参数指定上一页最后一个资源的标识符。

如果没有传递 count 参数或者 count 参数的值为空,则使用默认值 20 , count 参数的最大上限为 100 。

如何同时传递了 last_cursorpage 参数,则使用 page

分页的相关信息会包含在 Link HeaderX-Total-Count 中。

如果是第一页或者是最后一页时,不会返回 previousnext 的 Link 。

HTTP/1.1 200 OK
X-Total-Count: 542
Link: <http://api.example.com/#{RESOURCE_URI}?last_cursor=&count=100>; rel="first",
      <http://api.example.com/#{RESOURCE_URI}?last_cursor=200&count=100>; rel="last"
      <http://api.example.com/#{RESOURCE_URI}?last_cursor=90&count=100>; rel="previous",
      <http://api.example.com/#{RESOURCE_URI}?last_cursor=120&count=100>; rel="next",

[
  ...
]

相关资料:

数据缓存

大部分接口应该在响应头中携带 Last-Modified, ETag, Vary, Date 信息,客户端可以在随后请求这些资源的时候,在请求头中使用 If-Modified-Since, If-None-Match 等请求头来确认资源是否经过修改。

如果资源没有进行过修改,那么就可以响应 304 Not Modified 并且不在响应实体中返回任何内容。

$ curl -i http://api.example.com/#{RESOURCE_URI}
HTTP/1.1 200 OK
Cache-Control: public, max-age=60
Date: Thu, 05 Jul 2012 15:31:30 GMT
Vary: Accept, Authorization
ETag: "644b5b0155e6404a9cc4bd9d8b1ae730"
Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT

Content
$ curl -i http://api.example.com/#{RESOURCE_URI} -H "If-Modified-Since: Thu, 05 Jul 2012 15:31:30 GMT"
HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=60
Date: Thu, 05 Jul 2012 15:31:45 GMT
Vary: Accept, Authorization
Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT
$ curl -i http://api.example.com/#{RESOURCE_URI} -H 'If-None-Match: "644b5b0155e6404a9cc4bd9d8b1ae730"'
HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=60
Date: Thu, 05 Jul 2012 15:31:55 GMT
Vary: Accept, Authorization
ETag: "644b5b0155e6404a9cc4bd9d8b1ae730"
Last-Modified: Thu, 05 Jul 2012 15:31:30 GMT

相关资料:

并发控制

不严谨的实现,或者缺少并发控制的 PUTPATCH 请求可能导致 “更新丢失”。这个时候可以使用 Last-Modified 和/或 ETag 头来实现条件请求,支持乐观并发控制。

下文只考虑使用 PUTPATCH 方法更新资源的情况。

  • 客户端发起的请求如果没有包含 If-Unmodified-Since 或者 If-Match 头,那就返回状态码 403 Forbidden ,在响应正文中解释为何返回该状态码
  • 客户端发起的请求提供的 If-Unmodified-Since 或者 If-Match 头与服务器记录的实际修改时间或 ETag 值不匹配的时候,返回状态码 412 Precondition Failed
  • 客户端发起的请求提供的 If-Unmodified-Since 或者 If-Match 头与服务器记录的实际修改时间或 ETag 的历史值匹配,但资源已经被修改过的时候,返回状态码 409 Conflict
  • 客户端发起的请求提供的条件符合实际值,那就更新资源,响应 200 OK 或者 204 No Content ,并且包含更新过的 Last-Modified 和/或 ETag 头,同时包含 Content-Location 头,其值为更新后的资源 URI

相关资料:

跨域

CORS

接口支持“跨域资源共享”(Cross Origin Resource Sharing, CORS)这里这里这份中文资料有一些指导性的资料。

简单示例:

$ curl -i https://api.example.com -H "Origin: http://example.com"
HTTP/1.1 302 Found
$ curl -i https://api.example.com -H "Origin: http://example.com"
HTTP/1.1 302 Found
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: ETag, Link, X-Total-Count
Access-Control-Allow-Credentials: true

预检请求的响应示例:

$ curl -i https://api.example.com -H "Origin: http://example.com" -X OPTIONS
HTTP/1.1 302 Found
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With
Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE
Access-Control-Expose-Headers: ETag, Link, X-Total-Count
Access-Control-Max-Age: 86400
Access-Control-Allow-Credentials: true

JSON-P

如果在任何 GET 请求中带有参数 callback ,且值为非空字符串,那么接口将返回如下格式的数据

$ curl http://api.example.com/#{RESOURCE_URI}?callback=foo
foo({
  "meta": {
    "status": 200,
    "X-Total-Count": 542,
    "Link": [
      {"href": "http://api.example.com/#{RESOURCE_URI}?cursor=0&count=100", "rel": "first"},
      {"href": "http://api.example.com/#{RESOURCE_URI}?cursor=90&count=100", "rel": "prev"},
      {"href": "http://api.example.com/#{RESOURCE_URI}?cursor=120&count=100", "rel": "next"},
      {"href": "http://api.example.com/#{RESOURCE_URI}?cursor=200&count=100", "rel": "last"}
    ]
  },
  "data": // data
})

其他资料

更细节的接口设计指南

这里还有一些其他参考资料:

原文地址:https://github.com/bolasblack/http-api-guide/blob/master/README.md


来源:Is memcached a dinosaur in comparison to Redis?(其他人的回答同样值得一看)


这两年Redis火得可以,Redis也常常被当作Memcached的挑战者被提到桌面上来。关于Redis与Memcached的比较更是比比皆是。然而,Redis真的在功能、性能以及内存使用效率上都超越了Memcached吗?

下面内容来自Redis作者在stackoverflow上的一个回答,对应的问题是《Is memcached a dinosaur in comparison to Redis?》(相比Redis,Memcached真的过时了吗?)

  • You should not care too much about performances. Redis is faster per core with small values, but memcached is able to use multiple cores with a single executable and TCP port without help from the client. Also memcached is faster with big values in the order of 100k. Redis recently improved a lot about big values (unstable branch) but still memcached is faster in this use case. The point here is: nor one or the other will likely going to be your bottleneck for the query-per-second they can deliver.

  • 没有必要过多的关心性能,因为二者的性能都已经足够高了。由于Redis只使用单核,而Memcached可以使用多核,所以在比较上,平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。说了这么多,结论是,无论你使用哪一个,每秒处理请求的次数都不会成为瓶颈。(比如瓶颈可能会在网卡)

  • You should care about memory usage. For simple key-value pairs memcached is more memory efficient. If you use Redis hashes, Redis is more memory efficient. Depends on the use case.

  • 如果要说内存使用效率,使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。当然,这和你的应用场景和数据特性有关。

  • You should care about persistence and replication, two features only available in Redis. Even if your goal is to build a cache it helps that after an upgrade or a reboot your data are still there.

  • 如果你对数据持久化和数据同步有所要求,那么推荐你选择Redis,因为这两个特性Memcached都不具备。即使你只是希望在升级或者重启系统后缓存数据不会丢失,选择Redis也是明智的。

  • You should care about the kind of operations you need. In Redis there are a lot of complex operations, even just considering the caching use case, you often can do a lot more in a single operation, without requiring data to be processed client side (a lot of I/O is sometimes needed). This operations are often as fast as plain GET and SET. So if you don’t need just GEt/SET but more complex things Redis can help a lot (think at timeline caching).

  • 当然,最后还得说到你的具体应用需求。Redis相比Memcached来说,拥有更多的数据结构和并支持更丰富的数据操作,通常在Memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的GET/SET一样高效。所以,如果你需要缓存能够支持更复杂的结构和操作,那么Redis会是不错的选择。


从远程仓库获取

上一篇中我们把在GitHub上新建的仓库设置成了远程仓库,并向这个仓库push了feature-D分支。现在,所有能够访问这个远程仓库的人都可以获取feature-D分支并加以修改。

我们从实际开发者角度出发,在另一个目录下新建一个本地仓库,学习从远程仓库获取的相关操作。这就相当于刚刚执行过push操作的的目标仓库又有了另一名开发者来共同开发。

git clone —— 获取远程仓库

首先要切换到其他目录下,将GitHub上的仓库clone到本地。注意不要与之前的仓库在同一目录下。

➜  ~  cd Github 
➜  Github  git clone https://github.com/VergilLai/git-test.git
Cloning into 'git-test'...
remote: Counting objects: 20, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 20 (delta 3), reused 20 (delta 3), pack-reused 0
Unpacking objects: 100% (20/20), done.
Checking connectivity... done.
➜  Github  cd git-test 
➜  git-test git:(master) ls
README.md

执行git clone命令后会默认处于master分支下,同时系统会自动将origin设置成该仓库的标识符。也就是说,当前本地仓库的master分支雨GitHub端远程仓库(origin)的master分支在内容上时完全相同的。

➜  git-test git:(master) git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/feature-D
  remotes/origin/master

git branch -a命令查看当前分支的相关信息。添加-a参数可以同时显示本地仓库和远程仓库的分支信息。

结果中显示了remotes/origin/feature-D,证明远程仓库中已经有了feature-D分支。

获取远程的feature-D分支

将feature-D分支获取至本地仓库。

➜  git-test git:(master) git checkout -b feature-D origin/feature-D 
Branch feature-D set up to track remote branch feature-D from origin.
Switched to a new branch 'feature-D'

-b参数的后面是本地仓库中新建分支的名称。为了便于理解,仍将其命名为feature-D,让它与远程仓库的对应分支保持通明。新建分支名称后面时获取来源的分支名称。例子中指定了origin/feature-D,就时说以名为orgin的仓库(这里指GitHub仓库)的feature-D分支为来源,在本地仓库中创建feature-D分支。

向本地的feature-D分支提交更改

现在假设是另一名开发者,要做一个新的提交。在README.md中添加一行文字,查看更改。

➜  git-test git:(feature-D) echo "feature-D" >> README.md 
➜  git-test git:(feature-D) ✗ git diff

diff --git a/README.md b/README.md
index 66308ad..d1ff7a6 100644
--- a/README.md
+++ b/README.md
@@ -3,3 +3,4 @@ feature-A
 fix-B
 feature-C
+feature-D

➜  git-test git:(feature-D) ✗ git commit -am "Add feature-D"
[feature-D 3605345] Add feature-D
 1 file changed, 1 insertion(+)

推送feature-D分支

现在来推送feature-D分支。

➜  git-test git:(feature-D) git push
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 284 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/VergilLai/git-test.git
   98c18e6..3605345  feature-D -> feature-D

从远程仓库获取feature-D分支,在本地仓库中提交更改,再将feature-D分支推送回远程仓库,通过这一系列操作,就可以与其他开发者相互合作,共同培育feature-D分支,实现某些功能。

git pull —— 获取最新的远程仓库分支

现在我们放下刚刚操作的目录,回到原先的那个目录下。这边的本地仓库中只创建feature-D分支,并没有在feature-D分支中进行任何提交。然后远程仓库的feature-D分支中已经有了刚刚推送的提交。这时就可以使用git pull命令,将本地的feature-D分支更新到最新状态。

➜  git-test git:(master) git pull origin feature-D 
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From https://github.com/VergilLai/git-test
 * branch            feature-D  -> FETCH_HEAD
   98c18e6..3605345  feature-D  -> origin/feature-D
Updating 98c18e6..3605345
Fast-forward
 README.md | 1 +
 1 file changed, 1 insertion(+)
➜  git-test git:(master) cat README.md 
# Git基本操作
feature-A
fix-B
feature-C
feature-D

GitHub端远程仓库中的feature-D分支是最新状态,所以本地中的feature-D就会得到更新。今后只需要像平常一样在本地进行提交再push给远程仓库,就可以与其他开发同时在一个分支中进行作业,不断给feature-D增加新功能。

如果两人同时修改了同意部分源代码,push时就很容易发生冲突。所以多名开发者在同一个分支中进行作业时,为了减少冲突的情况发生免疫频繁地进行pushpull操作。


推送至远程仓库

Git时分散型的版本管理系统,但之前的文件所述,都是针对单一本地仓库的操作。下面,将开始接触远在网络另一头的远程仓库。远程仓库顾名思义,是与本地仓库相对独立的另一个仓库。我们现在GitHub上创建一个仓库,并将其设置为本地仓库的远程仓库。

为防止与其他仓库混淆,仓库名尽量与本地仓库保持一致(即git-test)。创建时不要勾选Initialize this repository with a README选项。因为一旦勾选该选项,GitHub一侧的仓库就会自动生成README.md文件,从创建之初便与本地仓库失去整合性。虽然到时也可以强制覆盖,但为防止这一情况发生还时建议不要勾选改选项,直接点击Create repository创建仓库。

git remote add —— 添加远程仓库

在GitHub上创建的仓库路径为git@github.com:用户名/仓库名.git或者https://github.com/用户名/仓库名.git。现在用git remote add命令将它设置成本地仓库的远程仓库。

➜  git-test git:(master) git remote add origin git@github.com:VergilLai/git-test.git

按照上述格式执行git remote add命令后,Git会自动将git@github.com:VergilLai/git-test.git远程仓库的名称设置为origin(标识符)。

git push —— 推送至远程仓库

推送至master分支

如果想将当前分支下本地仓库中的内容推送给远程仓库,需要用git push命令。现在假定在master分支下操作。

➜  git-test git:(master) git push -u origin master
Counting objects: 20, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (20/20), 1.53 KiB | 0 bytes/s, done.
Total 20 (delta 3), reused 0 (delta 0)
To https://github.com/VergilLai/git-test.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

像这样执行git push命令,当前分支的内容就会被推送给远程仓库origin的master分支。-u参数可以在推送的同时,将origin仓库的master分支设置为本地仓库当前分支的upstream(上游)。添加了这个参数,将来运行git pull命令从远程仓库获取内容时,本地仓库的这个分支就可以直接从origin的master分支获取内容,省去了另外添加参数的麻烦。

执行该操作后,当前本地仓库master分支的内容就会被推送到GitHub远程仓库中。在GitHub上也可以确认远程master分支的内容和本地master相同。

github

推送至master以外的分支

除了master分支之外,远程仓库也可以创建其他分支。举个例子,在本地仓库创建feature-D分支,并将它以同名的形式push至远程仓库。

➜  git-test git:(master) git checkout -b feature-D
Switched to a new branch 'feature-D'
➜  git-test git:(feature-D) git push -u origin feature-D
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/VergilLai/git-test.git
 * [new branch]      feature-D -> feature-D
Branch feature-D set up to track remote branch feature-D from origin.

现在,在远程仓库的GitHub页面就可以查看到feature-D分支了。

github


更改提交的操作

git reset —— 回溯历史版本

通过前面学习的操作,我们已经学会如何在实现功能后进行提交,累积提交日志作为历史记录,借此不断培育一款软件。

Git的另一特征便是可以灵活操作历史版本。借助分散仓库的优势,可以在不影响其他仓库的前提下对历史版本进行操作。

在这里,为了让各位熟悉对历史版本的操作,我们先回溯历史版本,创建一个名为fix-B的特性分支

回溯历史,创建fix-B分支

回溯到创建feature-A分支前

让我们先回溯到feature-A分支创建之前,创建一个名为fix-B的特性分支。

要让仓库的HEAD、暂存区、当前工作树回溯到指定状态,需要用到git reset --hard。只要提供目标时间点的哈希值,就可以完全恢复至该时间点的状态。

哈希值在每个环境中各不相同,可以使用git log查看。

➜  git-test git:(master) git log

commit c00815d1247d0ef656b3ea31c8cd2079e04bf52b
Merge: 7c1e8df ad5a4ba
Author: Vergil <vergil@vip.163.com>
Date:   Mon Sep 21 00:21:55 2015 +0800

    Merge branch 'feature-A'

commit ad5a4baec2ac4125c489b2461cb203ec14cd2658
Author: Vergil <vergil@vip.163.com>
Date:   Sun Sep 20 23:48:30 2015 +0800

    Add feature-A

commit 7c1e8dfd917605d9e6d7451f77a2e02bb7a2966b
Author: Vergil <vergil@vip.163.com>
Date:   Sun Sep 20 21:20:27 2015 +0800

    Add index

commit 69ca7dddd02bca848adccd5d00a62903081de979
Author: Vergil <vergil@vip.163.com>
Date:   Sun Sep 20 20:34:43 2015 +0800

    First commit

执行以下命令:

➜  git-test git:(master) git reset --hard 7c1e8dfd917605d9e6d7451f77a2e02bb7a2966b
HEAD is now at 7c1e8df Add index

现在已经成功回溯到特定分支(feature-A)创建之前的状态。由于所有文件都回溯到了指定哈希值对应的时间点上。README.md文件的内容也恢复到了当时的状态。

创建fix-B分支

现在来创建特性分支(fix-B)。

➜  git-test git:(master) git checkout -b fix-B
Switched to a new branch 'fix-B'

作为这个主题的作业内容,我们在README.md文件中添加一行文字:

➜  git-test git:(fix-B) echo "fix-B" >> README.md 

然后直接提交README.md文件。

➜  git-test git:(fix-B) ✗ git add README.md 
➜  git-test git:(fix-B) ✗ git commit -m "Fix B"
[fix-B 47aa230] Fix B
 1 file changed, 1 insertion(+)

现在的状态如图所示:

当前fix-B的状态

当前fix-B的状态

接下来的目标是下图所示的状态,即主干分支合并feature-A分支的修改后,又合并了fix-B的修改

fix-B分支的下一步目标

fix-B分支的下一步目标

推送至feature-A分支合并后的状态

首先恢复到feature-A分支合并后的状态。不妨称这一操作为“推进历史”。

git log命令只能查看以当前状态为终点的历史日志。所以这里要使用git reflog命令,查看当前仓库的操作日志。在日志中找出回溯历史之前的哈希值,通过 git reset --hard命令恢复到回溯历史前的状态。

首先执行git reflog命令,查看当前仓库执行过的操作日志。

➜  git-test git:(fix-B)  git reflog

47aa230 HEAD@{0}: commit: Fix B
7c1e8df HEAD@{1}: checkout: moving from master to fix-B
7c1e8df HEAD@{2}: reset: moving to 7c1e8dfd917605d9e6d7451f77a2e02bb7a2966b
c00815d HEAD@{3}: reset: moving to c00815d1247d0ef656b3ea31c8cd2079e04bf52b
7c1e8df HEAD@{4}: reset: moving to 7c1e8dfd917605d9e6d7451f77a2e02bb7a2966b
c00815d HEAD@{5}: merge feature-A: Merge made by the 'recursive' strategy.
7c1e8df HEAD@{6}: checkout: moving from feature-A to master
ad5a4ba HEAD@{7}: checkout: moving from master to feature-A
7c1e8df HEAD@{8}: checkout: moving from feature-A to master
ad5a4ba HEAD@{9}: commit: Add feature-A
7c1e8df HEAD@{10}: checkout: moving from feature-B to feature-A
7c1e8df HEAD@{11}: checkout: moving from feature-A to feature-B
7c1e8df HEAD@{12}: checkout: moving from master to feature-A
7c1e8df HEAD@{13}: commit: Add index
69ca7dd HEAD@{14}: commit (initial): First commit

在日志仲,可以看到commit、checkout、reset、merge等Git命令的执行记录。只要不进行Git的GC(Garbage Collection,垃圾回收),就可以通过日志随意调取近期的历史状态,就像给时间机器指定一个时间点,在过去未来中自由穿梭一般。即便开发者错误指定了Git操作,基本也都可以利用git reflog命令恢复到最原先的状态(如果人生可以像Git一样操作那该多好-_#)。

c00815d HEAD@{5}: merge feature-A: Merge made by the 'recursive' strategy.这一行表示feature-A特性分支合并后的状态,对应哈希值为c00815d(哈希值只输入4位以上就可以执行)。将HEAD、暂存区、工作树恢复到这个时间点的状态。

➜  git-test git:(fix-B) git checkout master 
Switched to branch 'master'
➜  git-test git:(master) git reset --hard c00815d
HEAD is now at c00815d Merge branch 'feature-A'

之前我们使用git reset --hard命令回溯了历史,这里又再次通过它恢复到了回溯前的历史状态。当前的状态如下图所示:

恢复历史后的状态

解决冲突

现在只要合并fix-B分支,就可以得到我们想要的状态。现在来进行合并操作。

➜  git-test git:(master) git merge --no-ff fix-B 
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

这时,系统告诉我们README.md文件发生了冲突(Conflict)。系统在合并README.md文件时,feature-A分支更改的部分与本次想要合并的fix-B分支更改的部分发生了冲突。

不解决冲突就无法完成合并,所以要打开README.md文件,解决这个冲突。

查看冲突部分并将其解决

用编辑器打开README.md文件,就会发现其内容变成了下面这个样子。

# Git基本操作
<<<<<<< HEAD
feature-A
=======
fix-B
>>>>>>> fix-B

=======以上的部分是当前HEAD的内容,以下的部分是要合并的fix-B分支中的内容。我们在编辑器中将起改成想要的样子。

# Git基本操作
feature-A
fix-B

如上所示,本次修正让feature-A与fix-B的内容并存于文件之中。但是在实际的软件开发中,往往需要删除其中之一,所以在处理冲突时,务必要仔细分析冲突部分的内容后再行修改。

提交解决后的结果

冲突解决后,执行git add命令和git commit命令。

➜  git-test git:(master) ✗ git add README.md 
➜  git-test git:(master) ✗ git commit -m "Fix conflict"
[master 0d9652a] Fix conflict

由于本次更改解决了冲突,所以提交信息记录为“Fix conflict”

git commit --amend —— 修改提交信息

要修改上一条提交信息,可以使用git commit --amend命令。

我们将上一条提交信息记录为“Fix conflict”,但它实际是fix-B分支的合并,解决合并时发生的冲突只是过程之一,这样标记实在不妥。于是,我们要修改这条提交信息。

➜  git-test git:(master) git commit --amend

执行上面的命令后,编辑器就会启动

Fix conflict

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Mon Sep 21 02:08:05 2015 +0800
#
# On branch master
# Changes to be committed:
#       modified:   README.md
#

编辑器中显示的内容如上所示,其中包含之前的提交信息。将提交信息的部分修改为Merge branch 'fix-B',然后保存文件,关闭编辑器。

[master 957d25d] Merge branch 'fix-B'

随后会显示上面这条结果。现在执行git log --graph命令,可以看到提交日志中的响应内容也已经被修改。

*   commit 957d25dd33395a5cec6a8a2d9c95c1e0256894b4
|\  Merge: c00815d 47aa230
| | Author: Vergil <vergil@vip.163.com>
| | Date:   Mon Sep 21 02:08:05 2015 +0800
| | 
| |     Merge branch 'fix-B'
| |   
| * commit 47aa2309167faed0c7a7da6b7c8bc162adafb4b6
| | Author: Vergil <vergil@vip.163.com>
| | Date:   Mon Sep 21 01:08:51 2015 +0800
| | 
| |     Fix B
| |     
* |   commit c00815d1247d0ef656b3ea31c8cd2079e04bf52b
|\ \  Merge: 7c1e8df ad5a4ba
| |/  Author: Vergil <vergil@vip.163.com>
|/|   Date:   Mon Sep 21 00:21:55 2015 +0800
| |   
| |       Merge branch 'feature-A'
| |   
| * commit ad5a4baec2ac4125c489b2461cb203ec14cd2658
|/  Author: Vergil <vergil@vip.163.com>
|   Date:   Sun Sep 20 23:48:30 2015 +0800
|   
|       Add feature-A
|  
* commit 7c1e8dfd917605d9e6d7451f77a2e02bb7a2966b
| Author: Vergil <vergil@vip.163.com>
| Date:   Sun Sep 20 21:20:27 2015 +0800
| 
|     Add index
|  
* commit 69ca7dddd02bca848adccd5d00a62903081de979
  Author: Vergil <vergil@vip.163.com>
  Date:   Sun Sep 20 20:34:43 2015 +0800
  
      First commit

git rebase -i —— 压缩历史

在合并特性分支之前,如果发现已提交的内容中有些许拼写错误等,不妨提交一个修改,然后将这个修改包含到前一个提交之中,压缩成一个历史记录。这是个经常会用到的技巧,我们来实际操作体会一下。

创建feature-C分支

首先,新建一个fearure-C特性分支。

➜  git-test git:(master) git checkout -b feature-C
Switched to a new branch 'feature-C'

作为fearure-C的功能实现,在README.md文件中添加一行文字,并且故意留下拼写错误,以便之后修正。

➜  git-test git:(feature-C) echo "faeture-C" >> README.md 

提交这部分内容。这个小小的变更就没必要先执行git add命令再执行git commit命令了,直接使用git commit -am来一次完成这两部操作。

➜  git-test git:(feature-C) ✗ git commit -am "Add feature-C"
[feature-C 3b3482c] Add feature-C
 1 file changed, 1 insertion(+)

修正拼写错误

看在来修正刚才预留的拼写错误。修正后的差别如下所示:

➜  git-test git:(feature-C) ✗ git diff

diff --git a/README.md b/README.md
index 9d181c5..847fb0f 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
 # Git基本操作
 feature-A
 fix-B
-faeture-C
+feature-C

然后进行提交。

➜  git-test git:(feature-C) ✗ git commit -am "Fix typo"     
[feature-C e1eb7f3] Fix typo
 1 file changed, 1 insertion(+)

错字漏字等失误称为typo,所以我们将提交信息记为“Fix typo”。

实际上,我们不希望在历史记录上看到这类提交,因为健全的历史记录不需要它们。如果能在最初提交之前就发现并修正这些错误,也就不会出现这类提交了。

更改历史

因此,我们来更改历史。将“Fix repo”修正的内容与之前一次的提交合并,在历史记录中合并为一次完美的提交。为此,要用到git rebase命令。

➜  git-test git:(feature-C) git rebase -i HEAD~2

pick 3b3482c Add feature-C
pick e1eb7f3 Fix typo

# Rebase 957d25d..e1eb7f3 onto 957d25d (       2 TODO item(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

我们将e1eb7f3的Fix typo的历史记录压缩到3b3482c的Add feature-C里。按以下所示,将e1eb7f3左侧的pick部分删除,改写为fixup

pick 3b3482c Add feature-C
fixup e1eb7f3 Fix typo

保存编辑器里的内容,关闭编辑器。

➜  git-test git:(feature-C) git rebase -i HEAD~2
[detached HEAD 26c1654] Add feature-C
 Date: Mon Sep 21 02:28:08 2015 +0800
 1 file changed, 2 insertions(+)
Successfully rebased and updated refs/heads/feature-C.

系统显示rebase成功。也就是以下面两个提交作为对象,将“Fix typo”的内容合并到了上一个提交“Add feature-C”中,改写成一个新的提交。

  • 3b3482c Add feature-C
  • e1eb7f3 Fix typo

现在再查看提交日志时发现Add feature-C的哈希值已经不是3b3482c,这证明提交已经被更改。

➜  git-test git:(feature-C) git log --graph 

* commit 26c16540cf42661c84da8e46b8cd8377783b5e5d
| Author: Vergil <vergil@vip.163.com>
| Date:   Mon Sep 21 02:28:08 2015 +0800
| 
|     Add feature-C
|    
*   commit 957d25dd33395a5cec6a8a2d9c95c1e0256894b4
|\  Merge: c00815d 47aa230
| | Author: Vergil <vergil@vip.163.com>
| | Date:   Mon Sep 21 02:08:05 2015 +0800
| | 
| |     Merge branch 'fix-B'
| |   
| * commit 47aa2309167faed0c7a7da6b7c8bc162adafb4b6
| | Author: Vergil <vergil@vip.163.com>
| | Date:   Mon Sep 21 01:08:51 2015 +0800
| | 
| |     Fix B
| |     
* |   commit c00815d1247d0ef656b3ea31c8cd2079e04bf52b
|\ \  Merge: 7c1e8df ad5a4ba
| |/  Author: Vergil <vergil@vip.163.com>
|/|   Date:   Mon Sep 21 00:21:55 2015 +0800
| |   
| |       Merge branch 'feature-A'
| |   
| * commit ad5a4baec2ac4125c489b2461cb203ec14cd2658
|/  Author: Vergil <vergil@vip.163.com>
|   Date:   Sun Sep 20 23:48:30 2015 +0800
|   
|       Add feature-A
|  
* commit 7c1e8dfd917605d9e6d7451f77a2e02bb7a2966b
| Author: Vergil <vergil@vip.163.com>
| Date:   Sun Sep 20 21:20:27 2015 +0800
| 
|     Add index
|  
* commit 69ca7dddd02bca848adccd5d00a62903081de979
  Author: Vergil <vergil@vip.163.com>
  Date:   Sun Sep 20 20:34:43 2015 +0800
  
      First commit

合并至master分支

feature-C分支的使命告一段落,将它与master合并。

➜  git-test git:(feature-C) git checkout master 
Switched to branch 'master'
➜  git-test git:(master) git merge --no-ff feature-C
Merge made by the 'recursive' strategy.
 README.md | 2 ++
 1 file changed, 2 insertions(+)

master分支整合了feature-C分支。


Powered by Typecho.