There hasn't been a blog post in 7 years, and there are no
plans to revive it. The existing posts generally fall into
one of two categories:

  - news about the pro git book

  - technical notes that weren't covered in the 1st edition
    of pro git

The former are no longer interesting at all. And the latter
had their content folded into the second edition.

So let's drop this content, as we sometimes get issues
raised about its accuracy and formatting. We want to
redirect people to the actually-maintained book content.

We'll put a short note in the /blog route to help anybody
who follows old links to these posts.
This commit is contained in:
Jeff King 2018-02-08 04:10:52 -05:00
Родитель 46247ff2a8
Коммит dd8b9d57e6
66 изменённых файлов: 8 добавлений и 2139 удалений

Просмотреть файл

@ -53,7 +53,7 @@ PG2:
* add video shownotes to search
* add stackoverflow to search
* only search your locale plus english
* search internal pages, too (blog, etc)
* search internal pages, too
--

Просмотреть файл

@ -1,12 +0,0 @@
// Place all the styles related to the blog controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
.subheader {
display: block;
padding-left: 2px;
font-size: 16px;
font-weight: normal;
line-height: 15px;
color: $light-font-color;
}

Просмотреть файл

@ -13,7 +13,6 @@
@import "book";
@import "book2";
@import "lists";
@import "blog";
code {
display: inline;

Просмотреть файл

@ -1,27 +1,2 @@
class BlogController < ApplicationController
# for progit.org blog
def post
year = params[:year]
month = params[:month]
day = params[:day]
slug = params[:slug]
file = [year, month, day, slug] * "-"
blog = BlogPresenter.new(file)
if blog.exists?
@content, @frontmatter = blog.render
else
raise PageNotFound
end
render :post
end
def index
end
# for Gitscm blog
def gitscm
end
private
end

Просмотреть файл

@ -3,7 +3,7 @@ require 'iso8601'
module ApplicationHelper
def sidebar_link_options(section)
if %w( about documentation reference book blog videos
if %w( about documentation reference book videos
external-links downloads guis logos community
).include?(@section) && @section == section
{class: "active"}

Просмотреть файл

@ -1,25 +0,0 @@
module BlogHelper
def list_posts
path = "#{Rails.root}/app/views/blog/posts/*.*"
posts = []
Dir[path].each do |uri|
file = uri.match(/^.*\/(.{10})(.*).(markdown|html)/)
posts << {:uri => file[0],
:file => file[1] + file[2],
:date_published => Date.parse(file[1]),
:title=> file[2].titleize,
:slug => file[2].parameterize
}
end
posts = posts.sort_by { |k| k[:date_published] }.reverse
end
def format_path(post)
"#{request.protocol}#{request.host_with_port}/blog/#{post[:date_published].year}/#{post[:date_published].strftime('%m')}/#{post[:date_published].strftime('%d')}/#{post[:slug]}.html"
end
def preview_post(postURI)
post = BlogPresenter.new(postURI)
post.render
end
end

Просмотреть файл

@ -1,40 +0,0 @@
class BlogPresenter
def initialize(file)
@file = file
@path = "#{Rails.root}/app/views/blog/posts/#{@file}"
end
def exists?
File.exists?("#{@path}.markdown") || File.exists?("#{@path}.html")
end
def render
if File.exists?("#{@path}.markdown")
source = File.read("#{@path}.markdown")
content, frontmatter = extract_frontmatter(source)
template = markdown.render(content)
[template, frontmatter]
elsif File.exists?("#{@path}.html")
source = File.read("#{@path}.html")
extract_frontmatter(source)
end
end
private
def markdown
@markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML)
end
def extract_frontmatter(content)
if content =~ /^(---\s*\n.*?\n?)^(---\s*$\n?)(.*)/m
cnt = $3
data = YAML.load($1)
[cnt, data]
else
[content, {}]
end
end
end

Просмотреть файл

@ -1,10 +0,0 @@
<h2>
All Posts
<% list_posts.each do |entry| %>
<p>
<strong>
<%= link_to entry[:title], format_path(entry) %>
</strong>
</p>
<% end %>
</h2>

Просмотреть файл

@ -1,20 +1,8 @@
<% @section = 'blog' %>
<% @newest = list_posts.first %>
<% @newest_content = preview_post(@newest[:file]) %>
<% @page_title = "Git - #{@newest_content[1]['title']}" %>
<% @page_title = "Git - Blog Moved" %>
<% content_for :sidebar do %>
<%= render 'list' %>
<% end %>
<div id="main">
<h1>Moved!</h1>
<p>Technical content that was previously found in the blog section of this site has been integrated into the <a href="/book">second edition of the Pro Git book</a>.
<div id="main" class="book">
<h1>
<%= @newest_content[1]['title'] %>
<% unless @newest_content[1]['author'].nil? %>
<small class="subheader">
by <%= @newest_content[1]['author'] %>
</small>
<% end %>
</h1>
<%= raw @newest_content[0] %>
</div>

Просмотреть файл

@ -1,18 +0,0 @@
<% @section = 'blog' %>
<% @page_title = @frontmatter['title'] %>
<% content_for :sidebar do %>
<%= render 'list' %>
<% end %>
<div id="main" class="book">
<h1>
<%= @frontmatter['title'] %>
<% unless @frontmatter['author'].nil? %>
<small class="subheader">
by <%= @frontmatter['author'] %>
</small>
<% end %>
</h1>
<%= raw @content %>
</div>

Просмотреть файл

@ -1,27 +0,0 @@
---
layout: post
title: Welcome to the Pro Git website
author:
---
This is the first post on the Pro Git book website, which contains the
full content of the book published by Apress and a blog for me to share
Git tips and book news with everybody.
I'm incredibly excited to get this book published - it has been a really
long time in the making, and I'm glad Apress let me publish the content
under a Creative Commons license, so I can share it online as well. If you
find the content helpful, please support this kind of open documentation
by buying a print version of the book at <a href="http://www.amazon.com/gp/product/1430218339?ie=UTF8&tag=prgi-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=1430218339">Amazon</a>.
The print version of the book is going to the presses soon and should be
shipping around the end of August. I was first contacted by Apress at the
very end of November of last year and had my first draft of the first chapter
in on December 15. Since then, it has been a whirlwind of writing, reviewing,
rewriting and re-reviewing of 9 long chapters. That's about 8 months from
start to finish and has been one of the most monumental side-projects I've
ever done. I've learned a ton about the publishing and book-writing process
and also about Git.
I hope you enjoy the book and I hope that it helps you to learn one of the
most amazing tools you can add to your development arsenal.

Просмотреть файл

@ -1,16 +0,0 @@
---
layout: post
title: The Gory Details
author:
---
First of all, thanks to everyone for spreading the word about this book and this site - I got _way_ more attention on the first day live than I expected I might. More than 10,000 individuals visited the site in the first 24 hours of it being online for over 38k pageviews. We got on Hacker News, Reddit and Delicious Popular aggregators, not to mention the Twitterverse.
In the comments of the initial post here, a user asked me about the "gory details" of writing this book. Specifically about "what tools you used to create the book and its figures". So here it is.
Somewhere else I read that someone liked that I used Markdown for writing the book, as you can download the Markdown source for the book <a href="https://github.com/progit/progit">at GitHub</a>. Well, the entire writing process was unfortunately not done in Markdown. At Apress most of the editing and review process is still MS Word centric. Since I threw hissy fits at Word for the first few chapters the very nice people at Apress allowed me to write the remainder of the book in Markdown initially and the technical reviews were done via a Git repository on GitHub using the Markdown source.
So, for most of the book, the process was : I would write the first draft of each chapter in Markdown, two reviewers would add comments inline. Then, I would fix whatever they commented on then move the text into Word to submit it to the copy editor. The copy editor would review the Word document and let the technical editor have another pass, then I would fix up anything they commented on. Finally I get the chapter back in PDF form and I would do a final pass. Then I took that text and put it back in Markdown to generate HTML from for the website.
Fun, huh?
For the diagrams, I always use OmniGraffle. I personally think it's one of the most amazing pieces of software ever created - I love it. I think normally an Apress designer would take whatever the author sketched and redo them, but in this case we actually just used the diagrams that I made. I just added the .graffle file to the <a href="https://github.com/progit/progit">GitHub repo</a> if you're interested in it.

Просмотреть файл

@ -1,40 +0,0 @@
---
layout: post
title: Translate This
author:
---
One of the things I love about Git is how easy it is to fork and contribute.
More than once on GitHub I've put up some content and a handful of helpful
souls go about translating it into another language. Well, it's happened
again with Pro Git. Since I released it under the Creative Commons license
and put the code <a href="https://github.com/progit/progit">up on GitHub</a>,
several people have forked and started translating the book into other
languages such as
<a href="/book/de">German</a>,
<a href="/book/zh">中文</a>,
<a href="/book/ja">Japanese</a> and
<a href="/book/ru">Russian</a>.
I've been merging this work into <a href="https://github.com/progit/progit">my
main repository</a> to make it easier for people who want to help and now I've
started publishing these translations on the main progit.org website. At the
bottom of the page in the footer is now a list of ongoing translations (none
of them are complete yet) that link to the translated content.
If you know another language and would like to make this Git resource
available to others in that language, please either jump into helping with
one of the ongoing translations or start a new one - I'll begin publishing it
at progit.org once it gets going.
The process to help with translations is to fork
<a href="https://github.com/progit/progit">my main content repository</a>,
clone your new fork, make some changes and then send me a pull request through
GitHub. Actually, even if you don't send me a pull request, I tend to check
out my network graph every few days and pull in things that look good.
There have so far been more than 30 people who have contributed translated
material, errata or formatting fixes for my book since I put it up here -
thanks to everyone! However, special thanks for all the help in translating
the book so far to Mike Limansky (ru), Victor Portnov (ru), Egor Levichev (ru), Sven Fuchs (de), marcel (de), Fritz Thielemann (de), Yu Inao (ja), chunzi(zh) and DaNmarner (zh). Also huge thanks to Shane (duairc)
for all the PDF creation software work. Keep it up!

Просмотреть файл

@ -1,126 +0,0 @@
---
layout: post
title: Undoing Merges
author:
---
I would like to start writing more here about general Git tips, tricks and
upcoming features. There has actually been a lot of cool stuff that has
happened since the book was first published, and a number of interesting
things that I didn't get around to covering in the book. I figure if I start
blogging about the more interesting stuff, it should serve as a pretty handy
guide should I ever start writing a second edition.
For the first such post, I'm going to cover a topic that was asked about
at a training I did recently. The question was about a workflow where long
running branches are merged occasionally, much like the <a href="http://progit.org/book/ch5-3.html#largemerging_workflows">Large Merging</a>
workflow that I describe in the book. They asked how to unmerge a branch,
either permenantly or allowing you to merge it in later.
You can actually do this a number of ways. Let's say you have history that
looks something like this:
<img src="/images/unmerge1.png">
You have a couple of topic branches that you have developed and then integrated
together by a series of merges. Now you want to revert something back in the
history, say 'C10' in this case.
The first way to solve the problem could be to rewind 'master' back to C3 and
then merge the remaining two lines back in again. This requires that anyone
you're collaborating with knows how to handle rewound heads, but if that's not
an issue, this is a perfectly viable solution. This is basically how the 'pu'
branch is handled in the Git project itself.
$ git checkout master
$ git reset --hard [sha_of_C3]
$ git merge jk/post-checkout
$ git merge db/push-cleanup
Once you rewind and remerge, you'll instead have a history that looks more
like this:
<img src="/images/unmerge2.png">
Now you can go back and work on that newly unmerged line and merge it again
at a later point, or perhaps ignore it entirely.
<h2>Reverting a Merge</h2>
However, what if you didn't find this out until later, or perhaps you or one
of your collaborators have done work after this merge series? What if your
history looks more like this:
<img src="/images/unmerge3.png">
Now you either have to revert one of the merges, or go back, remerge and then
cherry-pick the remaining changes again (C10 and C11 in this case), which is
confusing and difficult, especially if there are a lot of commits after those
merges.
Well, it turns out that Git is actually pretty good at reverting an entire merge.
Although you've probably only used the `git revert` command to revert a single
commit (if you've used it at all), you can also use it to revert merge commits.
All you have to do is specify the merge commit you want to revert and the parent
line you want to keep. Let's say that we want to revert the merge of the
`jk/post-checkout` line. We can do so like this:
$ git revert -m 1 [sha_of_C9]
Finished one revert.
[master 88edd6d] Revert "Merge branch 'jk/post-checkout'"
1 files changed, 0 insertions(+), 2 deletions(-)
That will introduce a new commit that undoes the changes introduced by merging
in the branch in the first place - sort of like a reverse cherry pick of all
of the commits that were unique to that branch. Pretty cool.
<img src="/images/unmerge4.png">
However, we're not done.
<h2>Reverting the Revert</h2>
Let's say now that you want to re-merge that work again. If you try to merge
it again, Git will see that the commits on that branch are in the history and
will assume that you are mistakenly trying to merge something you already have.
$ git merge jk/post-checkout
Already up-to-date.
Oops - it did nothing at all. Even more confusing is if you went back and committed on that branch
and then tried to merge it in, it would only introduce the changes _since_ you
originally merged.
<img src="/images/unmerge5.png">
Gah. Now that's really a strange state and is likely to cause a bunch of
conflicts or confusing errors. What you want to do instead is revert the revert
of the merge:
$ git revert 88edd6d
Finished one revert.
[master 268e243] Revert "Revert "Merge branch 'jk/post-checkout'""
1 files changed, 2 insertions(+), 0 deletions(-)
<img src="/images/unmerge6.png">
Cool, so now we've basically reintroduced everything that was in the branch
that we had reverted out before. Now if we have more work on that branch
in the meantime, we can just re-merge it.
$ git merge jk/post-checkout
Auto-merging test.txt
Merge made by recursive.
test.txt | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
<img src="/images/unmerge7.png">
So, I hope that's helpful. This can be particularly useful if you have a merge-heavy
development process. In fact, if you work mostly in topic branches before
merging for integration purposes, you may want to use the `git merge --no-ff`
option so that the first merge is not a fast forward and can be reverted out
in this manner.
Until next time.

Просмотреть файл

@ -1,161 +0,0 @@
---
layout: post
title: Smart HTTP Transport
author:
---
When I was done writing Pro Git, the only transfer protocols that existed were
the `git://`, `ssh://` and basic `http://` transports. I wrote about the basic
strengths and weaknesses of each in <a href="/book/ch4-1.html">Chapter 4</a>.
At the time, one of the big differences between Git and most other VCS's was
that HTTP was not a mainly used protocol - that's because it was read-only and
very inefficient. Git would simply use the webserver to ask for individual
objects and packfiles that it needed. It would even ask for big packfiles even
if it only needed one object from it.
As of the release of version 1.6.6 at the end of last year, however, Git can
now use the HTTP protocol just about as efficiently as the `git` or `ssh`
versions (thanks to the amazing work by Shawn Pearce, who also happened to have
been the technical editor of Pro Git). Amusingly, it has been given very little
fanfare - the release notes for 1.6.6 state only this:
* "git fetch" over http learned a new mode that is different from the
traditional "dumb commit walker".
Which is a huge understatement, given that I think this will become the
standard Git protocol in the very near future. I believe this because it's
both efficient and can be run either secure and authenticated (https) or open
and unauthenticated (http). It also has the huge advantage that most
firewalls have those ports (80 and 443) open already and normal users don't
have to deal with `ssh-keygen` and the like. Once most clients have updated
to at least v1.6.6, `http` will have a big place in the Git world.
<h2>What is "Smart" HTTP?</h2>
Before version 1.6.6, Git clients, when you clone or fetch over HTTP would
basically just do a series of GETs to grab individual objects and packfiles on
the server from bare Git repositories, since it knows the layout of the repo.
This functionality is documented fairly completely in <a href="http://progit.org/book/ch9-6.html">Chapter 9</a>.
Conversations over this protocol used to look like this:
$ git clone https://github.com/schacon/simplegit-progit.git
Initialized empty Git repository in /private/tmp/simplegit-progit/.git/
got ca82a6dff817ec66f44342007202690a93763949
walk ca82a6dff817ec66f44342007202690a93763949
got 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Getting alternates list for https://github.com/schacon/simplegit-progit.git
Getting pack list for https://github.com/schacon/simplegit-progit.git
Getting index for pack 816a9b2334da9953e530f27bcac22082a9f5b835
Getting pack 816a9b2334da9953e530f27bcac22082a9f5b835
which contains cfda3bf379e4f8dba8717dee55aab78aef7f4daf
walk 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
walk a11bef06a3f659402fe7563abf99ad00de2209e6
It is a completly passive server, and if the client needs one object in a packfile
of thousands, the server cannot pull the single object out, the client is forced
to request the entire packfile.
<img src="/images/smarthttp1.png">
In contrast, the smarter protocols (`git` and `ssh`) would instead have a
conversation with the `git upload-pack` process on the server which would
determine the exact set of objects the client needs and build a custom packfile
with just those objects and stream it over.
The new clients will now send a request with an extra GET parameter that older
servers will simply ignore, but servers running the smart CGI will recognize
and switch modes to a multi-POST mode that is similar to the conversation that
happens over the `git` protocol. Once this series of POSTs is complete, the
server knows what objects the client needs and can build a custom packfile and
stream it back.
<img src="/images/smarthttp2.png">
Furthermore, in the olden days if you wanted to push over http, you had to setup
a DAV-based server, which was rather difficult and also pretty inefficient compared
to the smarter protocols. Now you can push over this CGI, which again is very
similar to the push mechanisms for the `git` and `ssh` protocols. You simply
have to authenticate via an HTTP-based method, like basic auth or the like
(assuming you don't want your repository to be world-writable).
The rest of this article will explain setting up a server with the "smart"-http
protocol, so you can test out this cool new feature. This feature is referred
to as "smart" HTTP vs "dumb" HTTP because it requires having the Git binary
installed on the server, where the previous incantation of HTTP transfer
required only a simple webserver. It has a real conversation with the client,
rather than just dumbly pushing out data.
<h2>Setting up Smart HTTP</h2>
So, Smart-HTTP is basically just enabling the new CGI script that is provided
with Git called
<a href="https://www.kernel.org/pub/software/scm/git/docs/git-http-backend.html">`git-http-backend`</a>
on the server. This CGI will read the path and
headers sent by the revamped `git fetch` and `git push` binaries who have
learned to communicate in a specific way with a smart server. If the CGI sees
that the client is smart, it will communicate smartly with it, otherwise it will
simply fall back to the dumb behavior (so it is backward compatible for reads
with older clients).
To set it up, it's best to walk through the instructions on the
<a href="https://www.kernel.org/pub/software/scm/git/docs/git-http-backend.html">`git-http-backend`</a>
documentation page. Basically, you have to install Git v1.6.6 or higher on
a server with an Apache 2.x webserver (it has to be Apache, currently - other
CGI servers don't work, last I checked). Then you add something similar to this
to your http.conf file:
SetEnv GIT_PROJECT_ROOT /var/www/git
SetEnv GIT_HTTP_EXPORT_ALL
ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/
Then you'll want to make writes be authenticated somehow, possibly with an Auth
block like this:
<LocationMatch "^/git/.*/git-receive-pack$">
AuthType Basic
AuthName "Git Access"
Require group committers
...
</LocationMatch>
That is all that is really required to get this running. Now you have a smart
http-based Git server that can do anonymous reads and authenticated writes with
clients that have upgraded to 1.6.6 and above.
How awesome is that? The <a href="https://www.kernel.org/pub/software/scm/git/docs/git-http-backend.html">documentation</a>
goes over more complex examples, like making it work with GitWeb and accelerating
the dumb fallback reads, if you're interested.
<h2>Rack-based Git Server</h2>
If you're not a fan of Apache or you're running some other web server, you may
want to take a look at an app that I wrote called <a href="https://github.com/schacon/grack">Grack</a>, which
is a <a href="http://rack.rubyforge.org/">Rack</a>-based application for Smart-HTTP Git.
<a href="http://rack.rubyforge.org/">Rack</a> is a generic webserver interface
for Ruby (similar to WSGI for Python) that has adapters for a ton of web servers.
It basically replaces `git http-backend` for non-Apache servers that can't run it.
This means that I can write the web handler independent of the web server and it
will work with any web server that has a Rack handler. This currently means any FCGI server,
Mongrel (and EventedMongrel and SwiftipliedMongrel), WEBrick, SCGI, LiteSpeed,
Thin, Ebb, Phusion Passenger and Unicorn. Even cooler, using
<a href="http://caldersphere.rubyforge.org/warbler/classes/Warbler.html">Warbler</a>
and JRuby, you can generate a WAR file that is deployable in any Java web
application server (Tomcat, Glassfish, Websphere, JBoss, etc).
So, if you don't use Apache and you are interested in a Smart-HTTP Git server,
you may want to check out Grack. At <a href="https://github.com">GitHub</a>, this
is the adapter we're using to eventually implement Smart-HTTP support for all
the GitHub repositories. (It's currently a tad bit behind, but I'll be starting
up on it again soon as I get it into production at GitHub - send pull requests if you
find any issues)
Grack is about half as fast as the Apache version for simple ref-listing stuff,
but we're talking 10ths of a second. For most clones and pushes, the data transfer
will be the main time-sink, so the load time of the app should be negligible.
<h2>In Conclusion</h2>
I think HTTP based Git will be a huge part of the future of Git, so if you're
running your own Git server, you should really check it out. If you're not,
GitHub and I'm sure other hosts will soon be supporting it - upgrade your Git
client to 1.7ish soon so you can take advantage of it when it happens.

Просмотреть файл

@ -1,250 +0,0 @@
---
layout: post
title: Rerere Your Boat...
author:
---
One of the things I didn't touch on at all in the book is the `git rerere`
functionality. This also came up recently
during one of my trainings, and I realize that a lot of people probably could
use this, so I wanted to let you all know about it.
The <a href="http://ftp.sunet.se/pub/Linux/kernel.org/software/scm/git/docs/git-rerere.html">`git rerere`</a>
functionality is a bit of a hidden feature (Git actually has
a lot of cool hidden features, if you haven't figured that out yet). The name
stands for "reuse recorded resolution" and as the name implies, it allows you
to ask Git to remember how you've resolved a hunk conflict so that the next time
it sees the same conflict, Git can automatically resolve it for you.
There are a number of scenarios in which this functionality might be really
handy. One of the examples that is mentioned in the documentation is if you
want to make sure a long lived topic branch will merge cleanly but don't want
to have a bunch of intermediate merge commits. With `rerere` turned on you
can merge occasionally, resolve the conflicts, then back out the merge. If
you do this continuously, then the final merge should be easy because `rerere`
can just do everything for you automatically.
This same tactic can be used if you want to keep a branch rebased so you don't
have to deal with the same rebasing conflicts each time you do it. Or if you
want to take a branch that you merged and fixed a bunch of conflicts and then
decide to rebase it instead - you likely won't have to do all the same conflicts
again.
The other situation I can think of is where you merge a bunch of evolving
topic branches together into a testable head occasionally. If the tests fail,
you can rewind the merges and re-do them without the topic branch that made the
tests fail without having to re-resolve the conflicts again.
To enable the rerere functionality, you simply have to run this config setting:
$ git config --global rerere.enabled true
You can also turn it on by creating the `.git/rr-cache` directory in a specific
repository, but I think the config setting is clearer, and it can be done globally.
Now let's see a simple example. If we have a file that looks like this:
#! /usr/bin/env ruby
def hello
puts 'hello world'
end
and in one branch we change the word 'hello' to 'hola', then in another branch
we change the 'world' to 'mundo'.
<img src="/images/rerere1.png">
When we merge the two branches together, we'll get a merge conflict:
$ git merge i18n-world
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Recorded preimage for 'hello.rb'
Automatic merge failed; fix conflicts and then commit the result.
You should notice the new line `Recorded preimage for FILE` in there. Otherwise
it should look exactly like a normal merge conflict. At this point, `rerere`
can tell us some stuff. Normally, you might run `git status` at this point to
see what all conflicted:
$ git status
# On branch master
# Unmerged paths:
# (use "git reset HEAD <file>..." to unstage)
# (use "git add <file>..." to mark resolution)
#
# both modified: hello.rb
#
However, `git rerere` will also tell you what it has recorded the pre-merge
state for with `git rerere status`:
$ git rerere status
hello.rb
And `git rerere diff` will show the current state of the resolution - what you
started with to resolve and what you've resolved it to.
$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,11 @@
#! /usr/bin/env ruby
def hello
-<<<<<<<
- puts 'hello mundo'
-=======
+<<<<<<< HEAD
puts 'hola world'
->>>>>>>
+=======
+ puts 'hello mundo'
+>>>>>>> i18n-world
end
Also (and this isn't really related to `rerere`), you can use `ls-files -u` to
see the conflicted files and the before, left and right versions:
$ git ls-files -u
100644 39804c942a9c1f2c03dc7c5ebcd7f3e3a6b97519 1 hello.rb
100644 a440db6e8d1fd76ad438a49025a9ad9ce746f581 2 hello.rb
100644 54336ba847c3758ab604876419607e9443848474 3 hello.rb
Anyhow, so now you resolve it to just be "puts 'hola mundo'" and you can run
the `rerere diff` command again to see what rerere will remember:
$ git rerere diff
--- a/hello.rb
+++ b/hello.rb
@@ -1,11 +1,7 @@
#! /usr/bin/env ruby
def hello
-<<<<<<<
- puts 'hello mundo'
-=======
- puts 'hola world'
->>>>>>>
+ puts 'hola mundo'
end
So that basically says, when I see a hunk conflict that has 'hello mundo' on
one side and 'hola world' on the other, resolve it to 'hola mundo'.
Now we can mark it as resolved and commit it:
$ git add hello.rb
$ git commit
Recorded resolution for 'hello.rb'.
[master 68e16e5] Merge branch 'i18n'
You can see that it "Recorded resolution for FILE".
<img src="/images/rerere2.png">
Now, let's undo
that merge and then rebase it on top of our master branch instead.
$ git reset --hard HEAD^
HEAD is now at ad63f15 i18n the hello
Our merge is undone. Now let's rebase the topic branch.
$ git checkout i18n-world
Switched to branch 'i18n-world'
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: i18n one word
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Resolved 'hello.rb' using previous resolution.
Failed to merge in the changes.
Patch failed at 0001 i18n one word
Now, we got the same merge conflict like we expected, but check out the `Resolved
FILE using previous resolution` line. If we look at the file, we'll see that it's
already been resolved:
$ cat hello.rb
#! /usr/bin/env ruby
def hello
puts 'hola mundo'
end
Also, `git diff` will show you how it was automatically re-resolved:
$ git diff
diff --cc hello.rb
index a440db6,54336ba..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
#! /usr/bin/env ruby
def hello
- puts 'hola world'
- puts 'hello mundo'
++ puts 'hola mundo'
end
<img src="/images/rerere3.png">
You can also recreate the conflicted file state with the `checkout` command:
$ git checkout --conflict=merge hello.rb
$ cat hello.rb
#! /usr/bin/env ruby
def hello
<<<<<<< ours
puts 'hola world'
=======
puts 'hello mundo'
>>>>>>> theirs
end
That might be a new command to you as well, the `--conflict` option to
`git checkout`. You can actually have `checkout` do a couple of things in
this situation to help you resolve conflicts. Another interesting value for
that option is 'diff3', which will give you left, right and common to help you
resolve the conflict manually:
$ git checkout --conflict=diff3 hello.rb
$ cat hello.rb
#! /usr/bin/env ruby
def hello
<<<<<<< ours
puts 'hola world'
|||||||
puts 'hello world'
=======
puts 'hello mundo'
>>>>>>> theirs
end
Anyhow, then you can re-resolve it by just running `rerere` again:
$ git rerere
Resolved 'hello.rb' using previous resolution.
$ cat hello.rb
#! /usr/bin/env ruby
def hello
puts 'hola mundo'
end
Magical re-resolving! Then you can add and continue the rebase to complete it.
$ git add hello.rb
$ git rebase --continue
Applying: i18n one word
So, if you do a lot of re-merges, or want to keep a topic branch up to date
with your master branch without a ton of merges, or you rebase often or any
of the above, turn on `rerere` to help your life out a bit.

Просмотреть файл

@ -1,147 +0,0 @@
---
layout: post
title: Git's Little Bundle of Joy
author:
---
The scenario is thus: you need to sneakernet a `git push`. Maybe your network
is down and you want to send changes to your co-workers. Perhaps you're working
somewhere onsite and don't have access to the local network for security reasons.
Maybe your wireless/ethernet card just broke. Maybe you don't have access to
a shared server for the moment, you want to email someone updates and you don't
want to transfer 40 commits via `format-patch`.
Enter `git bundle`. The `bundle` command will package up everything that would
normally be pushed over the wire with a `git push` command into a binary file
that you can email or sneakernet around, then unbundle into another repository.
Let's see a simple example. Let's say you have a repository with two commits:
$ git log
commit 9a466c572fe88b195efd356c3f2bbeccdb504102
Author: Scott Chacon <schacon@gmail.com>
Date: Wed Mar 10 07:34:10 2010 -0800
second commit
commit b1ec3248f39900d2a406049d762aa68e9641be25
Author: Scott Chacon <schacon@gmail.com>
Date: Wed Mar 10 07:34:01 2010 -0800
first commit
If you want to send that repository to someone and you don't have access to
a repository to push to, or simply don't want to set one up, you can bundle it.
$ git bundle create repo.bundle master
Counting objects: 6, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (6/6), 441 bytes, done.
Total 6 (delta 0), reused 0 (delta 0)
Now you have a file named `repo.bundle` that has all the data needed to re-create
the repository. You can email that to someone else, or put it on a USB drive
and walk it over.
Now on the other side, say you are sent this `repo.bundle` file and want to work
on the project.
$ git clone repo.bundle -b master repo
Initialized empty Git repository in /private/tmp/bundle/repo/.git/
$ cd repo
$ git log --oneline
9a466c5 second commit
b1ec324 first commit
I had to specify `-b master` because otherwise it couldn't find the HEAD
reference for some reason, but you may not need to do that. The point is, you
have now cloned directly from a file, rather than from a remote server.
Now let's say you do three commits on it and want to send the new commits back
via a bundle on a usb stick or email.
$ git log --oneline
71b84da last commit - second repo
c99cf5b fourth commit - second repo
7011d3d third commit - second repo
9a466c5 second commit
b1ec324 first commit
First we need to determine the range of commits we want to include in the bundle.
The easiest way would have been to drop a branch when we started, so we could
say `start_branch..master` or `master ^start_branch`, but if we didn't we can
just list the starting SHA explicitly:
$ git log --oneline master ^9a466c5
71b84da last commit - second repo
c99cf5b fourth commit - second repo
7011d3d third commit - second repo
So we have the list of commits we want to include in the bundle, let's bundle
em up. We do that with the `git bundle create` command, giving it a filename
we want our bundle to be and the range of commits we want to go into it.
$ git bundle create commits.bundle master ^9a466c5
Counting objects: 11, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (9/9), 775 bytes, done.
Total 9 (delta 0), reused 0 (delta 0)
Now we will have a `commits.bundle` file in our directory. If we take that and
send it to our partner, she can then import it into the original repository,
even if more work has been done there in the meantime.
When she gets the bundle, she can inspect it to see what it contains before she
imports it into her repository. The first command is the `bundle verify` command
that will make sure the file is actually a valid Git bundle and that you have
all the necessary ancestors to reconstitute it properly.
$ git bundle verify ../commits.bundle
The bundle contains 1 ref
71b84daaf49abed142a373b6e5c59a22dc6560dc refs/heads/master
The bundle requires these 1 ref
9a466c572fe88b195efd356c3f2bbeccdb504102 second commit
../commits.bundle is okay
If the bundler had created a bundle of just the last two commits they had done,
rather than all three, the original repository would not be able to import it,
since it is missing requisite history. The `verify` command would have looked
like this instead:
$ git bundle verify ../commits-bad.bundle
error: Repository lacks these prerequisite commits:
error: 7011d3d8fc200abe0ad561c011c3852a4b7bbe95 third commit - second repo
However, our first bundle is valid, so we can fetch in commits from it. If you
want to see what branches are in the bundle that can be imported, there is also
a command to just list the heads:
$ git bundle list-heads ../commits.bundle
71b84daaf49abed142a373b6e5c59a22dc6560dc refs/heads/master
The `verify` sub-command will tell you the heads, too, as will a normal
`git ls-remote` command, which you may have used for debugging before. The point
is to see what can be pulled in, so you can use the `fetch` or `pull` commands
to import commits from this bundle. Here we'll fetch the 'master' branch of
the bundle to a branch named 'other-master' in our repository:
$ git fetch ../commits.bundle master:other-master
From ../commits.bundle
* [new branch] master -> other-master
Now we can see that we have the imported commits on the 'other-master' branch
as well as any commits we've done in the meantime in our own 'master' branch.
$ git log --oneline --decorate --graph --all
* 8255d41 (HEAD, master) third commit - first repo
| * 71b84da (other-master) last commit - second repo
| * c99cf5b fourth commit - second repo
| * 7011d3d third commit - second repo
|/
* 9a466c5 second commit
* b1ec324 first commit
So, `git bundle` can be really useful for doing network-y, share-y operations
when you don't have the proper network or shared repository to do so.

Просмотреть файл

@ -1,187 +0,0 @@
---
layout: post
title: Replace Kicker
author:
---
In another of my series of "seemingly hidden Git features", I would like to
introduce `git replace`. Now, documentation exists for `git replace`, but it
is rather unclear on what you would actually use it for (or even what it really
does), so let's go through a couple of examples as it really is quite powerful.
The `replace` command basically will take one object in your Git database and
for most purposes replace it with another object. This is most commonly useful
for replacing one commit in your history with another one.
For example, say you want to split your history into one short history for
new developers and one much longer and larger history for people interested
in data mining. You can graft one history onto the other by `replace`ing
the earliest commit in the new line with the latest commit on the older one. This
is nice because it means that you don't actually have to rewrite every commit
in the new history, as you would normally have to do to join them together
(because the parentage affects the SHAs).
Let's try this out. Let's take an existing repository, split it into two
repositories, one recent and one historical, and then we'll see how we can
recombine them without modifying the recent repositories SHA values via `replace`.
We'll use a simple repository with five simple commits:
$ git log --oneline
ef989d8 fifth commit
c6e1e95 fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
We want to break this up into two lines of history. One line goes from commit
one to commit four - that will be the historical one. The second line will
just be commits four and five - that will be the recent history.
<center><img src="/images/replace1.png"></center>
Well, creating the historical history is easy, we can just put a branch in the
history and then push that branch to the master branch of a new remote repository.
$ git branch history c6e1e95
$ git log --oneline --decorate
ef989d8 (HEAD, master) fifth commit
c6e1e95 (history) fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
<center><img src="/images/replace2.png"></center>
Now we can push the new history branch to the master branch of our new repository:
$ git remote add history git@github.com:schacon/project-history.git
$ git push history history:master
Counting objects: 12, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (12/12), 907 bytes, done.
Total 12 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (12/12), done.
To git@github.com:schacon/project-history.git
* [new branch] history -> master
OK, so our history is published. Now the harder part is truncating our current
history down so it's smaller. We need an overlap so we can replace a commit
in one with an equivalent commit in the other, so we're going to truncate this
to just commits four and five (so commit four overlaps).
$ git log --oneline --decorate
ef989d8 (HEAD, master) fifth commit
c6e1e95 (history) fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
I think it's useful in this case to create a base commit that has instructions
on how to expand the history, so other developers know what to do if they
hit the first commit in the truncated history and need more. So, what we're
going to do is create an initial commit object as our base point with instructions,
then rebase the remaining commits (four and five) on top of it. To do that,
we need to choose a point to split at, which for us is the third commit, which
is `9c68fdc` in SHA-speak. So, our base commit will be based off of that tree.
We can create our base commit using the `commit-tree` command, which just takes
a tree and will give us a brand new, parentless commit object SHA back.
$ echo 'get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf
<center><img src="/images/replace3.png"></center>
OK, so now that we have a base commit, we can rebase the rest of our history
on top of that with `git rebase --onto`. The `--onto` argument will be the
SHA we just got back from `commit-tree` and the rebase point will be the third
commit (`9c68fdc` again):
$ git rebase --onto 622e88 9c68fdc
First, rewinding head to replay your work on top of it...
Applying: fourth commit
Applying: fifth commit
<center><img src="/images/replace4.png"></center>
OK, so now we've re-written our recent history on top of a throw away base commit
that now has instructions in it on how to reconstitute the entire history if
we wanted to. Now let's see what those instructions would be (here is where
`replace` finally comes into play).
So to get the history data after cloning this truncated repository, one would
have to add a remote for the historical repository and fetch:
$ git remote add history git://github.com/schacon/project-history.git
$ git fetch history
From git://github.com/schacon/project-history.git
* [new branch] master -> history/master
Now the collaborator would have their recent commits in the 'master' branch
and the historical commits in the 'history/master' branch.
$ git log --oneline master
e146b5f fifth commit
81a708d fourth commit
622e88e get history from blah blah blah
$ git log --oneline history/master
c6e1e95 fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
To combine them, you can simply call `git replace` with the commit you want
to replace and then the commit you want to replace it with. So we want to
replace the 'fourth' commit in the master branch with the 'fourth' commit in
the 'history/master' branch:
$ git replace 81a708d c6e1e95
Now, if you look at the history of the `master` branch, it looks like this:
$ git log --oneline
e146b5f fifth commit
81a708d fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit
Cool, right? Without having to change all the SHAs upstream, we were able to
replace one commit in our history with an entirely different commit and all the
normal tools (`bisect`, `blame`, etc) will work how we would expect them to.
<center><img src="/images/replace5.png"></center>
Interestingly, it still shows `81a708d` as the SHA, even though it's actually
using the `c6e1e95` commit data that we replaced it with. Even if you run a
command like `cat-file`, it will show you the replaced data:
$ git cat-file -p 81a708d
tree 7bc544cf438903b65ca9104a1e30345eee6c083d
parent 9c68fdceee073230f19ebb8b5e7fc71b479c0252
author Scott Chacon <schacon@gmail.com> 1268712581 -0700
committer Scott Chacon <schacon@gmail.com> 1268712581 -0700
fourth commit
Remember that the actual parent of `81a708d` was our placeholder commit (`622e88e`),
not `9c68fdce` as it states here.
The other cool thing is that this is kept in our references:
$ git for-each-ref
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/heads/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/remotes/history/master
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/HEAD
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/replace/81a708dd0e167a3f691541c7a6463343bc457040
This means that it's easy to share our replacement with others, because we can
push this to our server and other people can easily download it. This is not that
helpful in the history grafting scenario we've gone over here (since everyone
would be downloading both histories anyhow, so why separate them?) but it can
be useful in other circumstances. I'll cover some other interesting scenarios
in another post - I think this is probably enough to process for now.

Просмотреть файл

@ -1,112 +0,0 @@
---
layout: post
title: Git Loves the Environment
author:
---
One of the things that people that come from the Subversion world tend
to find pretty cool about Git is that there is no `.svn` directory in
every subdirectory of your project, but instead just one `.git` directory
in the root of your project. Actually, it's even better than that.
The `.git` directory does not even need to actually be within your project.
Git allows you to tell it where your `.git` directory is, and there are
a couple of ways to do that.
Let's say you have your project and want to move the `.git` directory
somewhere else. First let's see what happens when we move our `.git`
directory without telling Git.
$ git log --oneline
6e948ec my second commit
fda8c93 my initial commit
$ mv .git /opt/myproject.git
$ git log
fatal: Not a git repository (or any of the parent directories): .git
Well, since Git can't find a `.git` directory, it appears that you are
simply in a directory that is not controlled by Git. However, it's
pretty easy to tell Git to look elsewhere by providing the `--git-dir`
option to any Git call:
$ git --git-dir=/opt/myproject.git log --oneline
6e948ec my second commit
fda8c93 my initial commit
However, you probably don't want to do that for every Git call, as
that is a lot of typing. You could create a shell alias, but you can
also export an environment variable called `GIT_DIR`.
$ export GIT_DIR=/opt/myproject.git
$ git log --oneline
6e948ec my second commit
fda8c93 my initial commit
There are a number of ways to customize Git functionality via specific
environment variables. You can also tell Git where your working directory
is with `GIT_WORK_TREE`, so you can run the Git commands from any
directory you are in, not just the current working directory. To see this,
first we'll change a file and then change directories and run `git status`.
$ echo 'test' >> README
$ git status --short
M README
OK, but now if we change working directories, we'll get weird output.
$ cd /tmp
$ git status --short
D README
?? .ksda.1F5/
?? aprKhGx02
?? qlm.log
?? qlmlog.txt
?? smsi02122
Now Git is comparing your last commit to what is in your current working
directory. However, you can tell it where your real Git working directory
is without being in it, either with the `--work-tree` option or by exporting
the `GIT_WORK_TREE` variable:
$ git --work-tree=/tmp/myproject status --short
M README
$ export GIT_WORK_TREE=/tmp/myproject
$ git status --short
M README
Now you're doing operations on a Git repository outside of your working
directory, which you're not even in.
The last interesting variable you can set is your staging area. That
is normally in the `.git/index` file, but again, you can set it somewhere
else, so that you can have multiple staging areas that you can switch
between if you want.
$ export GIT_INDEX_FILE=/tmp/index1
$ git add README
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: README
#
Now we have the README file changed staged in the new index file. If we
switch back to our original index, we can see that the file is no longer
staged:
$ export GIT_INDEX_FILE=/opt/myproject.git/index
$ git status
# On branch master
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: README
#
no changes added to commit (use "git add" and/or "git commit -a")
This is not quite as useful in day to day work, but it is pretty cool for
building arbitrary trees and whatnot. We'll explore how to use that to
do neat things in a future post when we talk more about some of the lower
level Git plumbing commands.

Просмотреть файл

@ -1,13 +0,0 @@
---
layout: post
title: Pro Git CliffNotes
author:
---
Jason Meridth has gone through the whole Pro Git book and compiled a list of notes of some
useful tips and whatnot in the book. If you have the print version, he marks which pages
all the tips were found on.
I thought it might be a useful companion, you can find it
<a href="http://www.lostechies.com/blogs/jason_meridth/archive/2010/04/05/quot-pro-git-quot-cliff-notes.aspx">here</a>.

Просмотреть файл

@ -1,38 +0,0 @@
---
layout: post
title: Pro Git on your iPad
author:
---
The awesome guys at <a href="http://mediatemple.net/">Media Temple</a> have converted
the Pro Git book into ePub format that looks great on the iBook reader on the iPad.
You can download it
<a href="https://github.s3.amazonaws.com/media/progit.epub">here</a>
and just drop the ePub file onto iTunes to upload it into your iPad. For reading
Pro Git on the road, this is a great format and MT did an amazing job at making it
look good on that platform.
Check it out - here is Pro Git in your bookshelf:
<img src="http://img.skitch.com/20100517-bn9jr7ax9k13ef2697srtjndyk.png"/>
The title page:
<img src="http://img.skitch.com/20100517-kac78crfcik9t36miyk7aam416.png"/>
And a chapter with illustrations:
<img src="http://img.skitch.com/20100517-jq3qnwr4w2xs27q8bnwpbay24k.png"/>
Sweet - thanks guys!
*Update:* I've added a Stanza Atom catalog feed for those of you who want to load
the epub book onto your Android running <a href="http://www.aldiko.com/">Aldiko</a>
or iPhone running <a href="http://www.lexcycle.com/">Stanza</a>. To download the
book, simply add a custom catalog with this URL:
<pre>
http://progit.org/stanza.atom
</pre>

Просмотреть файл

@ -1,15 +0,0 @@
---
layout: post
title: Kindle Version Available
author:
---
When Pro Git was first released, I asked about being able to get it on my Kindle. In fact, one of the very first people to read the book was <a href="http://twitter.com/adelcambre">@adelcambre</a> with a mobi file I generated myself. It was horrible looking, because I didn't do it very well, but it did work. My editor at Apress wanted to get a professional Kindle version produced, but wasn't sure if it was going to get done anytime soon.
Well, I just randomly found out that it did in fact finally get done. About 9 months after it was first published, I saw on my Amazon referal account that I had sold a Kindle version, which confused me. However, I went to Amazon and there it was:
<a href="http://www.amazon.com/gp/product/B00LPDVAX2?ie=UTF8&tag=prgi-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=B00LPDVAX2"><img border="0" src="http://ecx.images-amazon.com/images/I/51joD88JCAL._SL500_AA266_PIkin2,BottomRight,-3,34_AA300_SH20_OU01_.jpg"></a><img src="http://www.assoc-amazon.com/e/ir?t=prgi-20&l=as2&o=1&a=B00LPDVAX2" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />
I assume it looks much, much better than the version I originally did for Andy. (I should probably get myself a copy). So, if you feel like reading it on the Kindle, <a href="http://www.amazon.com/gp/product/B00LPDVAX2?ie=UTF8&tag=prgi-20&linkCode=as2&camp=1789&creative=390957&creativeASIN=B00LPDVAX2">have at it</a>. There have also been a number of people who have asked me how they can support the book without getting a dead-tree version, so here's your chance. You can even get a Kindle reader for your computer, so you can search through it and whatnot. Enjoy!
\[editor's note: since this post was published, the complete contents of the book have been made available as [`.mobi` and `.epub` e-books](https://git-scm.com/book/en/v2); you may still buy the Kindle version if you want to financially support the authors].

Просмотреть файл

@ -1,25 +0,0 @@
---
layout: post
title: Pro Git 简体中文版
author:
---
The amazing <a href="http://chunzi.me/">Chunzi</a>, in addition to translating
and helping to coordinate the translation of the Chinese version of Pro Git,
has just sent me a draft epub of the book in Chinese.
As he says:
"下载地址http://cl.ly/da7a450319adfac01108
此书基本完成翻译,但仍有大半尚未审阅。今心血来潮,做了 epub 版本,可在 iPad 上阅读。生成代码在 https://github.com/chunzi/progit/tree/master/epub-zh。
既然是开源图书,所以优先级比较低。后续更新时再作新版。"
I have also uploaded the file to S3 in addition to his cl.ly link. If you want
Pro Git in Chinese on your iPad, you can download it
<a href="https://github.s3.amazonaws.com/media/progit-zh.epub">here</a>.
祝你好运!

Просмотреть файл

@ -1,257 +0,0 @@
---
layout: post
title: Note to Self
author:
---
One of the cool things about Git is that it has strong cryptographic
integrity. If you change any bit in the commit data or any of the files it
keeps, all the checksums change, including the commit SHA and every commit
SHA since that one. However, that means that in order to amend the commit
in any way, for instance to add some comments on something or even sign off
on a commit, you have to change the SHA of the commit itself.
Wouldn't it be nice if you could add data to a commit without changing its
SHA? If only there existed an external mechanism to attach data to a commit
without modifying the commit message itself. Happy day! It turns out there
exists just such a feature in newer versions of Git! As we can see from the
Git 1.6.6 release notes where this new functionality was first introduced:
* "git notes" command to annotate existing commits.
Need any more be said? Well, maybe. How do you use it? What does it do?
How can it be useful? I'm not sure I can answer all of these questions, but
let's give it a try. First of all, how does one use it?
Well, to add a note to a specific commit, you only need to run
`git notes add [commit]`, like this:
$ git notes add HEAD
This will open up your editor to write your commit message. You can also use
the `-m` option to provide the note right on the command line:
$ git notes add -m 'I approve - Scott' master~1
That will add a note to the first parent on the last commit on the master
branch. Now, how to view these notes? The easiest way is with the `git log`
command.
$ git log master
commit 0385bcc3bc66d1b1ec07346c237061574335c3b8
Author: Ryan Tomayko <rtomayko@gmail.com>
Date: Tue Jun 22 20:09:32 2010 -0700
yield to run block right before accepting connections
commit 06ca03a20bb01203e2d6b8996e365f46cb6d59bd
Author: Ryan Tomayko <rtomayko@gmail.com>
Date: Wed May 12 06:47:15 2010 -0700
no need to delete these header names now
Notes:
I approve - Scott
You can see the notes appended automatically in the log output. You can only
have one note per commit in a namespace though (I will explain namespaces in
the next section), so if you want to add a note to that commit, you have to
instead edit the existing one. You can either do this by running:
$ git notes edit master~1
Which will open a text editor with the existing note so you can edit it:
I approve - Scott
#
# Write/edit the notes for the following object:
#
# commit 06ca03a20bb01203e2d6b8996e365f46cb6d59bd
# Author: Ryan Tomayko <rtomayko@gmail.com>
# Date: Wed May 12 06:47:15 2010 -0700
#
# no need to delete these header names now
#
# kidgloves.rb | 2 --
# 1 files changed, 0 insertions(+), 2 deletions(-)
~
~
~
".git/NOTES_EDITMSG" 13L, 338C
Sort of weird, but it works. If you just want to add something to the end of
an existing note, you can run `git notes append SHA`, but only in newer
versions of Git (I think 1.7.1 and above).
## Notes Namespaces ##
Since you can only have one note per commit, Git allows you to have multiple
namespaces for your notes. The default namespace is called 'commits', but
you can change that. Let's say we're using the 'commits' notes namespace to
store general comments but we want to also store bugzilla information for our
commits. We can also have a 'bugzilla' namespace. Here is how we would add
a bug number to a commit under the bugzilla namespace:
$ git notes --ref=bugzilla add -m 'bug #15' 0385bcc3
However, now you have to tell Git to specifically look in that namespace:
$ git log --show-notes=bugzilla
commit 0385bcc3bc66d1b1ec07346c237061574335c3b8
Author: Ryan Tomayko <rtomayko@gmail.com>
Date: Tue Jun 22 20:09:32 2010 -0700
yield to run block right before accepting connections
Notes (bugzilla):
bug #15
commit 06ca03a20bb01203e2d6b8996e365f46cb6d59bd
Author: Ryan Tomayko <rtomayko@gmail.com>
Date: Wed May 12 06:47:15 2010 -0700
no need to delete these header names now
Notes:
I approve - Scott
Notice that it also will show your normal notes. You can actually have it show
notes from all your namespaces by running `git log --show-notes=*` - if you
have a lot of them, you may want to just alias that. Here is what your log
output might look like if you have a number of notes namespaces:
$ git log -1 --show-notes=*
commit 0385bcc3bc66d1b1ec07346c237061574335c3b8
Author: Ryan Tomayko <rtomayko@gmail.com>
Date: Tue Jun 22 20:09:32 2010 -0700
yield to run block right before accepting connections
Notes:
I approve of this, too - Scott
Notes (bugzilla):
bug #15
Notes (build):
build successful (8/13/10)
You can also switch the current namespace you're using so that the default for
writing and showing notes is not 'commits' but, say, 'bugzilla' instead. If you
export the variable `GIT_NOTES_REF` to point to something different, then the
`--ref` and `--show-notes` options are not necessary. For example:
$ export GIT_NOTES_REF=refs/notes/bugzilla
That will set your default to 'bugzilla' instead. It has to start with the 'refs/notes/' though.
## Sharing Notes ##
Now, here is where the general usability of this really breaks down. I am
hoping that this will be improved in the future and I put off writing this post
because of my concern with this phase of the process, but I figured it has
interesting enough functionality as-is that someone might want to play with it.
So, the notes (as you may have noticed in the previous section) are stored as
references, just like branches and tags. This means you can push them to a
server. However, Git has a bit of magic built in to expand a branch name
like 'master' to what it really is, which is 'refs/heads/master'. Unfortunately,
Git has no such magic built in for notes. So, to push your notes to a server,
you cannot simply run something like `git push origin bugzilla`. Git will do
this:
$ git push origin bugzilla
error: src refspec bugzilla does not match any.
error: failed to push some refs to 'git@github.com:schacon/kidgloves.git'
However, you can push anything under 'refs/' to a server, you just need to be
more explicit about it. If you run this it will work fine:
$ git push origin refs/notes/bugzilla
Counting objects: 3, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 263 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To git@github.com:schacon/kidgloves.git
* [new branch] refs/notes/bugzilla -> refs/notes/bugzilla
In fact, you may want to just make that `git push origin refs/notes/*` which
will push all your notes. This is what Git does normally for something like
tags. When you run `git push origin --tags` it basically expands to
`git push origin refs/tags/*`.
## Getting Notes ##
Unfortunately, getting notes is even more difficult. Not only is there no
`git fetch --notes` or something, you have to specify both sides of the
refspec (as far as I can tell).
$ git fetch origin refs/notes/*:refs/notes/*
remote: Counting objects: 12, done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 12 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (12/12), done.
From github.com:schacon/kidgloves
* [new branch] refs/notes/bugzilla -> refs/notes/bugzilla
That is basically the only way to get them into your repository from the server.
Yay. If you want to, you can setup your Git config file to automatically pull
them down though. If you look at your `.git/config` file you should have a
section that looks like this:
[remote "origin"]
fetch = +refs/heads/*:refs/remotes/origin/*
url = git@github.com:schacon/kidgloves.git
The 'fetch' line is the refspec of what Git will try to do if you run just
`git fetch origin`. It contains the magic formula of what Git will fetch and
store local references to. For instance, in this case it will take every
branch on the server and give you a local branch under 'remotes/origin/' so
you can reference the 'master' branch on the server as 'remotes/origin/master'
or just 'origin/master' (it will look under 'remotes' when it's trying to
figure out what you're doing). If you change that line to
`fetch = +refs/heads/*:refs/remotes/manamana/*` then even though your remote
is named 'origin', the master branch from your 'origin' server will be under
'manamana/master'.
Anyhow, you can use this to make your notes fetching easier. If you add
multiple `fetch` lines, it will do them all. So in addition to the current
`fetch` line, you can add a line that looks like this:
fetch = +refs/notes/*:refs/notes/*
Which says also get all the notes references on the server and store them
as though they were local notes. Or you can namespace them if you want, but
that can cause issues when you try to push them back again.
## Collaborating on Notes ##
Now, this is where the main problem is. Merging notes is super difficult.
This means that if you pull down someone's notes, you edit any note in a
namespace locally and the other developer edits any note in that same namespace,
you're going to have a hard time getting them back in sync. When the second
person tries to push their notes it will look like a non-fast-forward just
like a normal branch update, but unlike a normal branch you can't just run
`git pull` and then try again. You have to check out your notes ref as if it
were a normal branch, which will look ridiculously confusing and then do the
merge and then switch back. It is do-able, but probably not something you
really want to do.
Because of this, it's probably best to namespace your notes or better
just have an automated process create them (like build statuses or bugzilla
artifacts). If only one entity is updating your notes, you won't have merge
issues. However, if you want to use them to comment on commits within a team,
it is going to be a bit painful.
So far, I've heard of people using them to have their ticketing system attach
metadata automatically or have
<a href="http://article.gmane.org/gmane.comp.version-control.git/109074">a system</a>
attach associated mailing list emails
to commits they concern.
Other people just use them entirely locally without pushing them
anywhere to store reminders for themselves and whatnot.
Probably a good start, but the ambitious among you may come up with
something else interesting to do. Let me know!

Просмотреть файл

@ -1,514 +0,0 @@
---
layout: post
title: Reset Demystified
author:
---
<p>One of the topics that I didn't cover in depth in the Pro Git book is the
<code>reset</code> command. Most of the reason for this, honestly, is that
I never strongly understood the command beyond the handful of
specific use cases that I needed it for. I knew what the command did, but
not really how it was designed to work.</p>
<p>Since then I have become more comfortable with the command, largely thanks to
<a href="http://blog.plover.com/prog/git-reset.html">Mark Dominus's article</a>
re-phrasing the content of the man-page, which I always found very difficult to
follow. After reading that explanation of the command, I now personally feel
more comfortable using <code>reset</code> and enjoy trying to help others feel
the same way.</p>
<p>This post assumes some basic understanding of how Git branching works. If you
don't really know what HEAD and the Index are on a basic level, you might want to
read chapters 2 and 3 of this book before reading this post.</p>
<h2>The Three Trees of Git</h2>
<img src="/images/reset/trees.png"/><br/>
<p>The way I now like to think about <code>reset</code> and <code>checkout</code>
is through the mental frame of Git being a content manager of three different
trees. By 'tree' here I really mean "collection of files", not specifically the
data structure. (Some Git developers will get a bit mad at me here, because there
are a few cases where the Index doesn't exactly act like a tree, but for our purposes
it is easier - forgive me).</p>
<p>Git as a system manages and manipulates three trees in its
normal operation. Each of these is covered in the book, but let's review them.</p>
<table id="threetrees">
<tr>
<th class="title" colspan="2">Tree Roles</th>
</tr><tr>
<th>The HEAD</th><td>last commit snapshot, next parent</td>
</tr><tr>
<th>The Index</th><td>proposed next commit snapshot</td>
</tr><tr>
<th>The Working Directory</th><td>sandbox</td>
</tr>
</table>
<h3 class="subtitle">
The HEAD
<small>last commit snapshot, next parent</small>
</h3>
<p>
The HEAD in Git is the pointer to the current branch reference, which is in
turn a pointer to the last commit you made or the last commit that was checked
out into your working directory. That also means it will be the parent of the
next commit you do. It's generally simplest to think of it as <b>HEAD is the
snapshot of your last commit</b>.
</p>
<p>In fact, it's pretty easy to see what the snapshot of your HEAD looks like.
Here is an example of getting the actual directory listing and SHA checksums
for each file in the HEAD snapshot:</p>
<pre>
$ cat .git/HEAD
ref: refs/heads/master
$ cat .git/refs/heads/master
e9a570524b63d2a2b3a7c3325acf5b89bbeb131e
$ git cat-file -p e9a570524b63d2a2b3a7c3325acf5b89bbeb131e
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
author Scott Chacon <schacon@gmail.com> 1301511835 -0700
committer Scott Chacon <schacon@gmail.com> 1301511835 -0700
initial commit
$ git ls-tree -r cfda3bf379e4f8dba8717dee55aab78aef7f4daf
100644 blob a906cb2a4a904a152... README
100644 blob 8f94139338f9404f2... Rakefile
040000 tree 99f1a6d12cb4b6f19... lib
</pre>
<h3 class="subtitle">
The Index
<small>next proposed commit snapshot</small>
</h3>
<p>
The Index is your proposed next commit. Git populates it with a list of all the
file contents that were last checked out into your working directory and what
they looked like when they were originally checked out. It's not technically a
tree structure, it's a flattened manifest, but for our purposes it's close
enough. When you run <code>git commit</code>, that command only looks at your
Index by default, not at anything in your working directory. So, it's simplest
to think of it as <b>the Index is the snapshot of your next commit</b>.
</p>
<pre>
$ git ls-files -s
100644 a906cb2a4a904a152e80877d4088654daad0c859 0 README
100644 8f94139338f9404f26296befa88755fc2598c289 0 Rakefile
100644 47c6340d6459e05787f644c2447d2595f5d3a54b 0 lib/simplegit.rb
</pre>
<h3 class="subtitle">
The Working Directory
<small>sandbox, scratch area</small>
</h3>
<p>
Finally, you have your working directory. This is where the content of files
are placed into actual files on your filesystem so they're easily edited by
you. <b>The Working Directory is your scratch space, used to easily modify file
content.</b>
</p>
<pre>
$ tree
.
├── README
├── Rakefile
└── lib
└── simplegit.rb
1 directory, 3 files
</pre>
<h2>The Workflow</h2>
<p>So, Git is all about recording snapshots of your project in successively
better states by manipulating these three trees, or collections of contents
of files.</p>
<center><img width="400px" src="/images/reset/workflow.png"/></center><br/>
<p>Let's visualize this process. Say you go into a new directory with a single
file in it. We'll call this V1 of the file and we'll indicate it in blue.
Now we run <code>git init</code>, which will create a Git repository with a
HEAD reference that points to an unborn branch (aka, <i>nothing</i>)</p>
<center><img width="500px" src="/images/reset/ex2.png"/></center><br/>
<p>At this point, only the <b>Working Directory</b> tree has any content.</p>
<p>Now we want to commit this file, so we use <code>git add</code> to take
content in your Working Directory and populate our Index with the updated
content</p>
<center><img width="500px" src="/images/reset/ex3.png"/></center><br/>
<p>Then we run <code>git commit</code> to take what the Index looks like now
and save it as a permanent snapshot pointed to by a commit, which HEAD is then
updated to point at.</p>
<center><img width="500px" src="/images/reset/ex4.png"/></center><br/>
<p>At this point, all three of the trees are the same. If we run
<code>git status</code> now, we'll see no changes because they're all the
same.</p>
<p>Now we want to make a change to that file and commit it. We will go
through the same process. First we change the file in our working directory.</p>
<center><img width="500px" src="/images/reset/ex5.png"/></center><br/>
<p>If we run <code>git status</code> right now we'll see the file in red as
"changed but not updated" because that entry differs between our Index and our
Working Directory. Next we run <code>git add</code> on it to stage it into our
Index.<p>
<center><img width="500px" src="/images/reset/ex6.png"/></center><br/>
<p>At this point if we run <code>git status</code> we will see the file in green
under 'Changes to be Committed' because the Index and HEAD differ - that is, our
proposed next commit is now different from our last commit. Those are the entries
we will see as 'to be Committed'. Finally, we run <code>git commit</code> to
finalize the commit.</p>
<center><img width="500px" src="/images/reset/ex7.png"/></center><br/>
<p>Now <code>git status</code> will give us no output because all three trees
are the same.</p>
<p>Switching branches or cloning goes through a similar process.
When you checkout a branch, it changes <b>HEAD</b> to point to the new commit,
populates your <b>Index</b> with the snapshot of that commit, then checks out the
contents of the files in your <b>Index</b> into your <b>Working Directory</b>.</p>
<h2>The Role of Reset</h2>
<p>So the <code>reset</code> command makes more sense when viewed in this
context. It directly manipulates these three trees in a simple and predictable
way. It does up to three basic operations.</p>
<h3 class="subtitle">
Step 1: Moving HEAD
<small>killing me --soft ly</small>
</h3>
<p>
The first thing <code>reset</code> will do is move what HEAD points to. Unlike
<code>checkout</code> it does not move what branch HEAD points to, it directly
changes the SHA of the reference itself. This means if HEAD is pointing to the
'master' branch, running <code>git reset 9e5e6a4</code> will first of all make
'master' point to <code>9e5e6a4</code> before it does anything else.
</p>
<center><img width="500px" src="/images/reset/reset-soft.png"/></center><br/>
<p>No matter what form of <code>reset</code> with a commit you invoke, this is
the first thing it will always try to do. If you add the flag <code>--soft</code>,
this is the <b>only</b> thing it will do. With <code>--soft</code>, <code>reset</code>
will simply stop there.</p>
<p>Now take a second to look at that diagram and realize what it did. It
essentially undid the last commit you made. When you run <code>git commit</code>,
Git will create a new commit and move the branch that <code>HEAD</code> points
to up to it. When you <code>reset</code> back to <code>HEAD~</code> (the parent
of HEAD), you are moving the branch back to where it was without changing the Index
(staging area) or Working Directory. You could now do a bit more work and
<code>commit</code> again to accomplish basically what <code>git commit --amend</code>
would have done.</p>
<h3 class="subtitle">
Step 2: Updating the Index
<small>having --mixed feelings</small>
</h3>
<p>Note that if you run <code>git status</code> now you'll see in green the
difference between the Index and what the new HEAD is.</p>
<p>The next thing <code>reset</code> will do
is to update the Index with the contents of whatever tree HEAD now
points to so they're the same.</p>
<center><img width="500px" src="/images/reset/reset-mixed.png"/></center><br/>
<p>If you specify the <code>--mixed</code> option, <code>reset</code> will stop
at this point. This is also the default, so if you specify no option at all,
this is where the command will stop.</p>
<p>Now take another second to look at THAT diagram and realize what it did. It
still undid your last <code>commit</code>, but also <i>unstaged</i> everything.
You rolled back to before you ran all your <code>git add</code>s <i>AND</i>
<code>git commit</code>.
</p>
<h3 class="subtitle">
Step 3: Updating the Working Directory
<small>math is --hard, let's go shopping</small>
</h3>
<p>The third thing that <code>reset</code> will do is to then make the Working
Directory look like the Index. If you use the <code>--hard</code> option, it
will continue to this stage.</p>
<center><img width="500px" src="/images/reset/reset-hard.png"/></center><br/>
<p>Finally, take yet a third second to look at <i>that</i> diagram and think
about what happened. You undid your last commit, all the <code>git add</code>s,
<i>and</i> all the work you did in your working directory.</p>
<p>It's important to note at this point that this is the only way to make the
<code>reset</code> command dangerous (ie: not working directory safe). Any
other invocation of <code>reset</code> can be pretty easily undone, the
<code>--hard</code> option cannot, since it overwrites (without checking) any
files in the Working Directory. In this particular case, we still have <b>v3</b>
version of our file in a commit in our Git DB that we could get back by looking
at our <code>reflog</code>, but if we had not committed it, Git still would have
overwritten the file.</p>
<h3>Overview</h3>
<p>
That is basically it. The <code>reset</code> command overwrites these three
trees in a specific order, stopping when you tell it to.
</p>
<ul>
<li>#1) Move whatever branch HEAD points to <small>(stop if <code>--soft</code>)</small>
<li>#2) THEN, make the Index look like that <small>(stop here unless <code>--hard</code>)</small>
<li>#3) THEN, make the Working Directory look like that
</ul>
<p>There are also <code>--merge</code> and <code>--keep</code> options, but
I would rather keep things simpler for now - that will be for another article.</p>
<p>Boom. You are now a <code>reset</code> master.</p>
<h2>Reset with a Path</h2>
<p>
Well, I lied. That's not actually all. If you specify a path,
<code>reset</code> will skip the first step and just do the other ones but limited
to a specific file or set of files. This actually sort of makes sense - if the
first step is to move a pointer to a different commit, you can't make it point
to <i>part</i> of a commit, so it simply doesn't do that part. However, you can
use <code>reset</code> to update part of the Index or the Working Directory
with previously committed content this way.
</p>
<p>So, assume we run <code>git reset file.txt</code>. This assumes, since you
did not specify a commit SHA or branch that points to a commit SHA, and that you
provided no reset option, that you are typing the shorthand for
<code>git reset --mixed HEAD file.txt</code>, which will:
<ul>
<li><strike>#1) Move whatever branch HEAD points to <small>(stop if <code>--soft</code>)</strike></small>
<li>#2) THEN, make the Index look like that <small><strike>(stop here unless <code>--hard</code>)</strike></small>
</ul>
<p>So it essentially just takes whatever <code>file.txt</code> looks like in
HEAD and puts that in the Index.</p>
<center><img width="500px" src="/images/reset/reset-path1.png"/></center><br/>
<p>So what does that do in a practical sense? Well, it <i>unstages</i> the
file. If we look at the diagram for that command vs what <code>git add</code>
does, we can see that it is simply the opposite. This is why the output of
the <code>git status</code> command suggests that you run this to unstage a
file.</p>
<center><img width="500px" src="/images/reset/reset-path2.png"/></center><br/>
<p>We could just as easily not let Git assume we meant "pull the data from HEAD"
by specifying a specific commit to pull that file version from to populate our
Index by running something like <code>git reset eb43bf file.txt</code>.
<center><img width="500px" src="/images/reset/reset-path3.png"/></center><br/>
<p>So what does that mean? That functionally does the same thing as if we had
reverted the content of the file to <b>v1</b>, ran <code>git add</code> on it,
then reverted it back to <b>v3</b> again. If we run <code>git commit</code>,
it will record a change that reverts that file back to <b>v1</b>, even though
we never actually had it in our Working Directory again.</p>
<p>It's also pretty interesting to note that like <code>git add --patch</code>,
the <code>reset</code> command will accept a <code>--patch</code> option to
unstage content on a hunk-by-hunk basis. So you can selectively unstage or
revert content.</p>
<h2>A fun example</h2>
<p>I may use the term "fun" here a bit loosely, but if this doesn't sound like
fun to you, you may drink while doing it. Let's look at how to do something
interesting with this newfound power - squashing commits.</p>
<p>If you have this history and you're about to push and you want to squash
down the last N commits you've done into one awesome commit that makes you
look really smart (vs a bunch of commits with messages like "oops.", "WIP"
and "forgot this file") you can use <code>reset</code> to quickly and easily
do that (as opposed to using <code>git rebase -i</code>).</p>
<p>So, let's take a slightly more complex example. Let's say you have a project
where the first commit has one file, the second commit added a new file and changed
the first, and the third commit changed the first file again. The second commit
was a work in progress and you want to squash it down.</p>
<center><img width="500px" src="/images/reset/squash-r1.png"/></center><br/>
<p>You can run <code>git reset --soft HEAD~2</code> to move the HEAD
branch back to an older commit (the first commit you want to keep):</p>
<center><img width="500px" src="/images/reset/squash-r2.png"/></center><br/>
<p>And then simply run <code>git commit</code> again:</p>
<center><img width="500px" src="/images/reset/squash-r3.png"/></center><br/>
<p>
Now you can see that your reachable history, the history you would push,
now looks like you had one commit with the one file, then a second that both
added the new file and modified the first to its final state.
</p>
<h2>Check it out</h2>
<p>Finally, some of you may wonder what the difference between <code>checkout</code>
and <code>reset</code> is. Well, like <code>reset</code>, <code>checkout</code>
manipulates the three trees and it is a bit different depending on whether you
give the command a file path or not. So, let's look at both examples separately.
</p>
<h3>git checkout [branch]</h3>
<p>Running <code>git checkout [branch]</code> is pretty similar to running
<code>git reset --hard [branch]</code> in that it updates all three trees for
you to look like <code>[branch]</code>, but there are two important differences.
</p>
<p>First, unlike <code>reset --hard</code>, <code>checkout</code> is working
directory safe in this invocation. It will check to make sure it's not blowing
away files that have changes to them. Actually, this is a subtle difference,
because it will update all of the working directory except the files you've
modified if it can - it will do a trivial merge between what you're checking
out and what's already there. In this case, <code>reset --hard</code> will
simply replace everything across the board without checking.
</p>
<p>The second important difference is how it updates HEAD. Where <code>reset</code>
will move the branch that HEAD points to, <code>checkout</code> will move HEAD
itself to point to another branch.</p>
<p>For instance, if we have two branches, 'master'
and 'develop' pointing at different commits, and we're currently on 'develop'
(so HEAD points to it) and we run <code>git reset master</code>, 'develop' itself
will now point to the same commit that 'master' does.</p>
<p>On the other hand, if we instead run <code>git checkout master</code>, 'develop'
will not move, HEAD itself will. HEAD will now point to 'master'. So, in both
cases we're moving HEAD to point to commit A, but <i>how</i> we do so is very
different. <code>reset</code> will move the branch HEAD points to,
<code>checkout</code> moves HEAD itself to point to another branch.</p>
<center><img width="500px" src="/images/reset/reset-checkout.png"/></center><br/>
<h3>git checkout [branch] file</h3>
<p>The other way to run <code>checkout</code> is with a file path, which like
<code>reset</code>, does not move HEAD. It is just like <code>git reset [branch] file</code>
in that it updates the index with that file at that commit, but it also
overwrites the file in the working directory. Think of it like
<code>git reset --hard [branch] file</code> - it would be exactly the same thing,
it is also not working directory safe and it also does not move HEAD. The only
difference is that <code>reset</code> with a file name will not accept <code>--hard</code>,
so you can't actually run that.</p>
<p>Also, like <code>git reset</code> and <code>git add</code>, <code>checkout</code>
will accept a <code>--patch</code> option to allow you to selectively revert file
contents on a hunk-by-hunk basis.</p>
<h2>Cheaters Gonna Cheat</h2>
<p>Hopefully now you understand and feel more comfortable with the
<code>reset</code> command, but are probably still a little confused about how
exactly it differs from <code>checkout</code> and could not possibly remember
all the rules of the different invocations.</p>
<p>So to help you out, I've created something that I pretty much hate, which
is a table. However, if you've followed the article at all, it may be a useful
cheat sheet or reminder. The table shows each class of the <code>reset</code>
and <code>checkout</code> commands and which of the three trees it updates.</p>
<p>Pay especial attention to the 'WD Safe?' column - if
it's red, really think about it before you run that command.</p>
<table class="rdata">
<tr>
<th></th>
<th>head</th>
<th>index</th>
<th>work dir</th>
<th>wd safe</th>
</tr>
<tr class="level">
<th>Commit Level</th>
<td colspan="4">&nbsp;</th>
</tr>
<tr class="even">
<th class="cmd">reset --soft [commit]</th>
<td class="yes">REF</td>
<td class="no">NO</td>
<td class="no">NO</td>
<td class="yes-wd">YES</td>
</tr>
<tr class="odd">
<th class="cmd">reset [commit]</th>
<td class="yes">REF</td>
<td class="yes">YES</td>
<td class="no">NO</td>
<td class="yes-wd">YES</td>
</tr>
<tr class="even">
<th class="cmd">reset --hard [commit]</th>
<td class="yes">REF</td>
<td class="yes">YES</td>
<td class="yes">YES</td>
<td class="no-wd">NO</td>
</tr>
<tr class="odd">
<th class="cmd">checkout [commit]</th>
<td class="yes">HEAD</td>
<td class="yes">YES</td>
<td class="yes">YES</td>
<td class="yes-wd">YES</td>
</tr>
<tr class="level">
<th>File Level</th>
<td colspan="4">&nbsp;</th>
</tr>
<tr class="even">
<th class="cmd">reset (commit) [file]</th>
<td class="no">NO</td>
<td class="yes">YES</td>
<td class="no">NO</td>
<td class="yes-wd">YES</td>
</tr>
<tr class="odd">
<th class="cmd">checkout (commit) [file]</th>
<td class="no">NO</td>
<td class="yes">YES</td>
<td class="yes">YES</td>
<td class="no-wd">NO</td>
</tr>
</table>
<p>Good night, and good luck.</p>

Просмотреть файл

@ -44,9 +44,6 @@
</li>
</ul>
</li>
<li>
<%= link_to "Blog", "/blog", sidebar_link_options("blog") %>
</li>
<li>
<%= link_to "Downloads", "/downloads", sidebar_link_options("downloads") %>
<ul class="<%= @section == "downloads" ? "expanded" : "" %>">

Просмотреть файл

@ -57,15 +57,8 @@ Gitscm::Application.routes.draw do
end
end
get "/:year/:month/:day/:slug" => "blog#post", :year => /\d{4}/,
:month => /\d{2}/,
:day => /\d{2}/
get "/blog/:year/:month/:day/:slug" => "blog#post", :year => /\d{4}/,
:month => /\d{2}/,
:day => /\d{2}/
get "/blog" => "blog#index"
get "/blog/*post" => redirect("/blog")
get "/about" => "about#index"
get "/about/:section" => "about#index"

Двоичные данные
public/images/replace1.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 6.9 KiB

Двоичные данные
public/images/replace2.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 7.2 KiB

Двоичные данные
public/images/replace3.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 8.9 KiB

Двоичные данные
public/images/replace4.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
public/images/replace5.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 9.9 KiB

Двоичные данные
public/images/rerere1.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 4.8 KiB

Двоичные данные
public/images/rerere2.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 8.2 KiB

Двоичные данные
public/images/rerere3.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 8.6 KiB

Двоичные данные
public/images/reset/ex1.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 3.2 KiB

Двоичные данные
public/images/reset/ex2.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 6.8 KiB

Двоичные данные
public/images/reset/ex3.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 7.9 KiB

Двоичные данные
public/images/reset/ex4.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 9.1 KiB

Двоичные данные
public/images/reset/ex5.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 9.0 KiB

Двоичные данные
public/images/reset/ex6.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 8.9 KiB

Двоичные данные
public/images/reset/ex7.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 9.9 KiB

Двоичные данные
public/images/reset/reset-checkout.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 11 KiB

Двоичные данные
public/images/reset/reset-hard.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
public/images/reset/reset-mixed.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
public/images/reset/reset-path1.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 9.4 KiB

Двоичные данные
public/images/reset/reset-path2.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 9.3 KiB

Двоичные данные
public/images/reset/reset-path3.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
public/images/reset/reset-soft.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
public/images/reset/reset1.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 10 KiB

Двоичные данные
public/images/reset/squash-r1.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 14 KiB

Двоичные данные
public/images/reset/squash-r2.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 14 KiB

Двоичные данные
public/images/reset/squash-r3.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 16 KiB

Двоичные данные
public/images/reset/trees.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 11 KiB

Двоичные данные
public/images/reset/workflow.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 16 KiB

Двоичные данные
public/images/smarthttp1.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 8.5 KiB

Двоичные данные
public/images/smarthttp2.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 9.7 KiB

Двоичные данные
public/images/unmerge1.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 11 KiB

Двоичные данные
public/images/unmerge2.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 10 KiB

Двоичные данные
public/images/unmerge3.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 9.5 KiB

Двоичные данные
public/images/unmerge4.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 10 KiB

Двоичные данные
public/images/unmerge5.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 12 KiB

Двоичные данные
public/images/unmerge6.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 11 KiB

Двоичные данные
public/images/unmerge7.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 13 KiB

Просмотреть файл

@ -1,20 +0,0 @@
require 'test_helper'
class BlogControllerTest < ActionController::TestCase
test "gets the blog index page" do
get :index
assert_response :success
end
test "gets the blog page" do
get :post, :year => 2009, :month => '02', :day => 11, :slug => "moved-to-github-pages"
assert_response :success
end
test "gets the blog feed" do
get :feed
assert_response :success
end
end

Просмотреть файл

@ -1,30 +0,0 @@
require 'test_helper'
class BlogPresenterTest < ActiveSupport::TestCase
setup do
@good = ["2010", "08", "25", "notes"] * "-"
@bad = ["2015", "01", "01", "failing-test"] * "-"
end
test "file should be existed" do
blog = BlogPresenter.new(@good)
assert_equal blog.exists?, true
end
test "file should not be existed" do
blog = BlogPresenter.new(@bad)
assert_equal blog.exists?, false
end
test "file should have content" do
blog = BlogPresenter.new(@good)
assert_not_nil blog.render
end
test "file should have no content" do
blog = BlogPresenter.new(@bad)
assert_nil blog.render
end
end