drop blog content
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.
2
TODO.txt
|
@ -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"> </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"> </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
|