2936 lines
158 KiB
Racket
2936 lines
158 KiB
Racket
#lang meta/web
|
|
|
|
(define-context "stubs/git"
|
|
#:robots (add-newlines (for/list ([d '(plt libs testing play)])
|
|
@list{Disallow: /@|d|/})))
|
|
|
|
(provide git)
|
|
(define git
|
|
@page[#:title "Development Repository" #:file ""]{
|
|
This is a stub page to get the header for the gitweb server.})
|
|
|
|
;; ----------------------------------------------------------------------------
|
|
;; gitweb stuff
|
|
|
|
(define header+footer
|
|
(lazy (cdr (or (regexp-match
|
|
;; extract just the meat between the <body>...</body>
|
|
#rx"<body[^<>]*>(.*?){{{BODY}}}(.*?)</body>"
|
|
(xml->string @page[#:id 'git #:html-only #t "{{{BODY}}}"]))
|
|
(error 'gitweb-skeleton "internal error")))))
|
|
(define header @plain[#:file "header.html" (car (force header+footer))])
|
|
(define footer @plain[#:file "footer.html" (cadr (force header+footer))])
|
|
|
|
(define gitweb-logo (copyfile (in-here "gitweb-logo.png") "tiny-logo.png"))
|
|
|
|
(define home-text
|
|
(lazy @text{
|
|
@p{This is the Racket git server.}
|
|
@p{See the "brief", PLT-oriented @intro{introduction to git}.}}))
|
|
(define home-file @plain[#:file "home-text.html" home-text])
|
|
|
|
(define gitweb-config
|
|
@plain[#:file "gitweb_config.perl"]{
|
|
our $projectroot = "repos";
|
|
@||
|
|
# used in the page's title tag
|
|
our $site_name = "Racket Repository";
|
|
# header/footer and home text html files
|
|
our $site_header = "header.html";
|
|
our $site_footer = "footer.html";
|
|
our $home_text = "@(regexp-replace #rx"^.*/" (home-file) "")";
|
|
push @"@"stylesheets, "@(the-resources 'style-path)";
|
|
@||
|
|
our $favicon = "@(the-resources 'icon-path)";
|
|
our $logo = "@gitweb-logo";
|
|
our $logo_url = "http://racket-lang.org/";
|
|
our $logo_label = "Racket Homepage";
|
|
@||
|
|
# specification of visible repositories (managed by gitolite)
|
|
our $projects_list = "plt-repos";
|
|
our $projects_list_description_width = 80;
|
|
our $default_projects_order = "age";
|
|
# hide repositories that are not listed in the above
|
|
our $strict_export = 1;
|
|
@||
|
|
# show repository only if this file exists
|
|
our $export_ok = "git-daemon-export-ok";
|
|
@||
|
|
# respond with "server busy" above this
|
|
our $maxload = 4;
|
|
@||
|
|
# label for the "home" link
|
|
our $home_link_str = "root";
|
|
@||
|
|
$feature{'pathinfo'}{'default'} = [1];
|
|
$my_uri = "http://git.racket-lang.org/";
|
|
$home_link = "http://git.racket-lang.org/";
|
|
# ? $feature{'javascript-actions'}{'default'} = [0];
|
|
@||
|
|
# some smaller features, usually overrideable by individual repos
|
|
$feature{'grep'}{'default'} = [1];
|
|
$feature{'grep'}{'override'} = 1;
|
|
$feature{'blame'}{'default'} = [1];
|
|
$feature{'blame'}{'override'} = 1;
|
|
$feature{'pickaxe'}{'default'} = [1];
|
|
$feature{'pickaxe'}{'override'} = 1;
|
|
$feature{'snapshot'}{'default'} = ['zip', 'tgz'];
|
|
# $feature{'snapshot'}{'override'} = 1;
|
|
$feature{'avatar'}{'default'} = ['gravatar'];
|
|
# $feature{'avatar'}{'override'} = 1;
|
|
})
|
|
|
|
;; ----------------------------------------------------------------------------
|
|
;; git "guide"
|
|
|
|
(define intro (let ()
|
|
|
|
(define (cmd . text) (span class: "code" text))
|
|
(define (path . text) (span class: "path" text))
|
|
(define (man name . text)
|
|
(a href: (list "http://www.kernel.org/pub/software/scm/git/docs/"
|
|
(and name (list name ".html")))
|
|
(if (null? text) (span class: "man" name) text)))
|
|
(define (selflink . url) (a href: url url))
|
|
(define git-host "git.racket-lang.org")
|
|
(define at-racket "@racket-lang.org")
|
|
(define at-git-racket "@git.racket-lang.org")
|
|
(define (npre . text) (apply pre style: "margin-left: 0;" text))
|
|
(define style
|
|
@style/inline[type: 'text/css]{
|
|
.p {
|
|
display: block;
|
|
margin: 1em 0;
|
|
@; text-indent: 1em;
|
|
}
|
|
.code, .path, .man, pre {
|
|
font-family: monospace;
|
|
font-size: large;
|
|
font-weight: bold;
|
|
background-color: #eeeeee;
|
|
}
|
|
.code, .path, .man {
|
|
white-space: nowrap;
|
|
}
|
|
.the_text a:link, .the_text a:visited {
|
|
text-decoration: underline;
|
|
}
|
|
.the_text pre {
|
|
margin-left: 2em;
|
|
padding: 0.6em 0 0.6em 0.6em;
|
|
}
|
|
.the_text ul, .the_text ol, .the_text dl,
|
|
.the_text li, .the_text dt, .the_text dd {
|
|
margin-top: 1em;
|
|
margin-bottom: 1em;
|
|
}})
|
|
|
|
;; xhtml strict doesn't allow lists inside <p>, so fake our own paragraphs
|
|
;; using divs:
|
|
(define p* (make-separated-tag values (λ text (apply div class: 'p text))))
|
|
|
|
@page[#:title "git intro" #:extra-headers style]{
|
|
|
|
@sections[#:newpages? #t]
|
|
|
|
@div[class: 'the_text]{
|
|
|
|
@section{Getting git}
|
|
@p*{
|
|
I @strong{highly} recommend getting a new git installation. Git itself is
|
|
pretty stable (that is, you probably will not run into bugs with whatever
|
|
version you have installed), but there are many usability related
|
|
improvements. Specifically, I am using 1.7.x and it is likely that some
|
|
things in this document are specific to that version.
|
|
@~
|
|
You can @a[href: "http://git-scm.com/download"]{download a recent version},
|
|
available in binary form for several popular platforms. In addition to
|
|
these, you can get a build for
|
|
@ul*{
|
|
@~ Ubuntu:
|
|
@pre{sudo add-apt-repository ppa:git-core/ppa
|
|
sudo apt-get update
|
|
sudo apt-get install git-core}
|
|
@~ OSX using macports:
|
|
@pre{sudo port selfupdate
|
|
sudo port install git-core +svn}}
|
|
(For OSX, you can also get @a[href: "http://gitx.frim.nl/"]{@cmd{GitX}} —
|
|
it's a good gui front-end for git, similar to @cmd{gitk} and @cmd{git gui}.)
|
|
@~
|
|
You can also build git from source is — here are the steps that I'm using to
|
|
install a new version:
|
|
@pre{GVER=1.7.10
|
|
BASE=http://git-core.googlecode.com/files
|
|
TARGET=/usr/local
|
|
cd /tmp; curl $BASE/git-$GVER.tar.gz | gunzip | tar xf -; cd git-$GVER
|
|
make prefix=$TARGET all && sudo make prefix=$TARGET install}
|
|
If you do this and you want the @man[#f]{man pages} too, then getting the
|
|
pre-built man pages is the easiest route (building them requires some
|
|
“exotic” tools):
|
|
@pre{cd $TARGET/share/man
|
|
curl $BASE/git-manpages-$GVER.tar.gz | gunzip | sudo tar xf -}}
|
|
|
|
@section{General git setup}
|
|
@p*{
|
|
Commits to a git repository are done locally, so git needs to know who you
|
|
are. (Unlike subversion, where you need to identify yourself to be able to
|
|
talk to the server, so the commit object is created there based on who you
|
|
authenticated as.) To get git to know you, run the following two commands:
|
|
@pre{git config --global user.name "My Name"
|
|
git config --global user.email "foo@at-racket"}
|
|
This sets your @em{default} name and email for @em{all} repositories — it
|
|
stores this information in @path{~/.gitconfig} which is the global
|
|
configuration file, used for such defaults. You can edit this file directly
|
|
too — it is in a fairly obvious textual format. There is a lot that can be
|
|
configured, see below for some of these (and see the @man{git-config} man
|
|
page for many more details).
|
|
@~
|
|
In addition to this file, each repository has its own configuration file
|
|
(located at @path{.git/config}). Whenever git needs to check some option, it
|
|
will use both the repository-specific config file (if you're in a repository)
|
|
and the global one. The @cmd{--global} flag above tells git to set the
|
|
option in the global file. Note that a configuration file cannot be part of
|
|
the repository itself — so when you get a repository, you still need to do
|
|
any local configuration you want. (This is intentional, since the
|
|
configuration file can specify various commands to run, so it avoids a major
|
|
security hazard.)
|
|
@~
|
|
Important: this sets your default identity name and email for @em{all}
|
|
repositories. This may be a problem if you want to commit to different git
|
|
repositories under different identities. See the section on customizing git
|
|
below for more details on this.}
|
|
|
|
@section{SSH setup}
|
|
@p*{
|
|
Since git is a distributed system, you can do everything locally on your own
|
|
repository, but obviously, the goal is to communicate with other people so
|
|
you'll need to push these changes somewhere else. The most popular way to
|
|
communicate with remote repositories — including repositories on the PLT
|
|
server, is via ssh. (Access is controlled via a tool called “gitolite” —
|
|
more on this below.) The username and hostname of the server is
|
|
@cmd{git@at-git-racket} — and you should be able to connect to this account
|
|
using the ssh identity key that corresponds to the public key that you use
|
|
with the git server. To try it, run
|
|
@pre{ssh git@at-git-racket}
|
|
and the server (gitolite, actually) should reply with information about your
|
|
current permissions. The exact details of this is not important for now,
|
|
just the fact that you were able to connect and get some reply.
|
|
@~
|
|
Using an ssh configuration file (usually @path{~/.ssh/config}), you
|
|
can set up a short name for the server. For example, you can have this:
|
|
@pre{Host pltgit
|
|
HostName @git-host
|
|
User git}
|
|
and now you can simply use @cmd{ssh pltgit info} instead of the last example:
|
|
@cmd{ssh} will know that @cmd{pltgit} is actually defined as
|
|
@cmd{git@at-git-racket}.
|
|
@~
|
|
This is the @strong{preferred} way to set things up: besides being more
|
|
convenient in that you need to type less — it is also a useful extra level of
|
|
indirection, so if the server settings ever change (for example, we might
|
|
switch to a non-standard port number), you can simply edit your ssh config
|
|
file, and continue working as usual. In addition, such a configuration is
|
|
needed if you created a specific ssh identity file to be used with git —
|
|
specifying an alternative identity file on the @cmd{ssh} command line is
|
|
possible (an @cmd{-i} flag, in the case of openssh), but remember that most
|
|
of your interactions with the remote server are done implicitly through git.
|
|
(It is possible to configure how git invokes ssh, but it is much easier to
|
|
just configure ssh). In this case, you will have:
|
|
@pre{Host pltgit
|
|
HostName @git-host
|
|
User git
|
|
IdentityFile ~/.ssh/my-plt-git-identity-file}
|
|
@~
|
|
In addition to an ssh configuration file, git also has a way to create prefix
|
|
shorthands. For example, if you use this configuration:
|
|
@pre|{git config --global url.git@foo.org:.insteadOf foo:}|
|
|
then whenever git expects a repository URL, it will replace @cmd{foo:}
|
|
with @cmd|{git@foo.org:}|, for example:
|
|
@pre{git clone foo:bar}
|
|
While it is possible to use this instead of an ssh config file to access the
|
|
@cmd{plt} repository, the former is preferable. The reason for that is that
|
|
you will also interact with the server directly via ssh commands (described
|
|
in the following section). Keeping the alias in your ssh configuration means
|
|
that you will use the same alias for both @cmd{git} commands and other
|
|
@cmd{ssh}-based commands. You may still want to use it for other servers,
|
|
specifically, here is a popular setup for github (this is configuration text
|
|
that you can paste into your global @cmd{.gitconfig} file):
|
|
@pre|{[url "git://github.com/"]
|
|
insteadOf = github:
|
|
[url "git@github.com:"]
|
|
pushInsteadOf = github:
|
|
pushInsteadOf = git://github.com/}|
|
|
It translates @cmd{github:} to a github read-only @cmd{git://} URL, and it
|
|
translates pushes to the same prefix to use github's ssh URLs. Note that it
|
|
also translates the read-only @cmd{git://} url to an ssh url for pushing.
|
|
(The same setup can be used for @cmd{gist.github.com}, to deal with github
|
|
gists via @cmd{git}.)}
|
|
|
|
@section{Gitolite: the server's gateway}
|
|
@p*{
|
|
All access to the PLT server is done via @cmd{ssh}, and this is where
|
|
gitolite comes in as the “who can do what” manager. What actually happens on
|
|
the server is that no matter what command you're trying to run (as you
|
|
usually would, for example: @cmd{ssh somewhere ls}), the server has settings
|
|
that make it always run its own command — and that is a gitolite script. The
|
|
script knows the command that you were actually trying to run, and it will
|
|
reply based on that. In the above ssh example, you're not specifying any
|
|
command (so if it wasn't for the pre-set gitolite script, you'd be asking for
|
|
a remote shell to start), and gitolite responds by telling you about your
|
|
permissions.
|
|
@~
|
|
This is actually the @cmd{info} command, so you get the same reply with
|
|
@cmd{ssh pltgit info}. Again, this connects to ssh and tries to run
|
|
@cmd{info}; gitolite sees that you're trying to run @cmd{info}, and instead
|
|
of running it, it responds with that information. There are a few additional
|
|
commands that you can use this way — these are all “meta commands” in the
|
|
sense that you're not interacting with a git process on the other end, but
|
|
rather get gitolite to perform various tasks on your behalf. You can run the
|
|
@cmd{help} command (@cmd{ssh pltgit help}) to see a list of available
|
|
commands. They are mostly useful in dealing with your private repositories
|
|
on the server, which will be discussed further below.}
|
|
|
|
@section{A (very) quick introduction to git}
|
|
@p*{
|
|
This is a quick description; see the last section for more resources
|
|
(specifically,
|
|
@a[href: "http://eagain.net/articles/git-for-computer-scientists/"]{
|
|
Git for Computer Scientists} covers these basics well). Understanding how
|
|
git models and stores data will make it significantly easier to work with it.
|
|
@~
|
|
A git repository is actually a database of a few kinds of objects, which form
|
|
a DAG. There are only a few of these kinds of objects, and they are all
|
|
addressed by the SHA1 checksum of their contents. You will generally see a
|
|
lot of these SHA1 strings (40 hexadecimal characters), since they form a kind
|
|
of a universal address for such objects. (For convenience, any unique prefix
|
|
of a SHA1 can be used with git commands when you need to refer to it.)
|
|
Whenever the following descriptions mention a pointer — this is actually such
|
|
a SHA1 hash.}
|
|
@ul*{
|
|
@~ A @em{blob} object is a generic container for any information, which
|
|
(usually) represents a file. This object has no pointers to any other
|
|
objects. It does not have anything except for the actual contents: no name,
|
|
permission bits, etc.
|
|
@~ A @em{tree} object represents a directory hierarchy: it contains a list of
|
|
names, and for each name a pointer to the object that is its contents. Some
|
|
of these will point at blobs (when the tree contains a file), and some of
|
|
these will point at other trees (when it contains a sub-tree). (These
|
|
objects are similar to directories in a file system in that they contain all
|
|
“meta” information on files: their names and permission bits are kept here.)
|
|
@~ A @em{commit} object represents a versioned snapshot of a tree, forming a
|
|
line of work. It has the following bits of information:
|
|
@ul*{@~ tree: a pointer to the tree object that was committed
|
|
@~ parent: a pointer to the previous commit, which this one revised
|
|
@~ author: the identity of the commit author (name, email, date)
|
|
@~ committer: the identity of the committer
|
|
@~ the text of the commit message (which can be arbitrarily long)}
|
|
The parent field is actually any number of parents: there will be no parents
|
|
if this is the first commit in the line of work, or more than one parent if
|
|
this is a “merge” commit that merges two lines of work. Furthermore, there
|
|
is nothing that prevents a git repository from having completely separate
|
|
lines of work — in fact, you can have several independent projects contained
|
|
in your repository.
|
|
@br
|
|
@small{(Note that git distinguishes the author of a commit from the person
|
|
who actually performed the commit, for example — a patch could be created
|
|
by X, and sent to Y to be committed.)}
|
|
@~ Finally, there is a @em{tag} object, which is very roughly a pointer to
|
|
another object (almost always a commit), and is not important for now.}
|
|
@p*{
|
|
The fact that all of these objects are addressed by the SHA1 hash of their
|
|
contents has some immediate important implications.}
|
|
@ul*{
|
|
@~ Since SHA1 are cryptographic checksums, they can be considered @em{unique}
|
|
for all practical purposes.
|
|
@~ The git repository is inherently hash-consed: you can never have “two
|
|
identical files” in git — because a file is stored at its SHA1 hash, two
|
|
identical files will always be stored once. (Note that the name of a file is
|
|
stored in the tree that contains it, so the SHA1 of the contents does not
|
|
depend on it.) The same holds for two trees: if you have two identical
|
|
directories (same contents of files, same names, etc), then there will
|
|
actually be only one tree stored in the repository.
|
|
@~ Furthermore, these addresses are actually global: any two repositories that
|
|
hold a file with the same contents will have it at the exact same SHA1 hash.
|
|
(For example, if I have a repository that contains several projects, and
|
|
each project contains several copies of the same LGPL text, then I'll have
|
|
only a single blob object with that contents.) This is not only making the
|
|
store efficient, it also makes it possible to refer to an object by its hash
|
|
— for example, you can refer to the SHA1 of a specific file at a specific
|
|
version in an email, and this will have the exact same meaning for anyone
|
|
that reads the file (eg, anyone can run @cmd{git show @i{SHA1}} to see that
|
|
file). (This does require that the readers have the actual object in their
|
|
repository, of course — but no mistakes can happen, statistically speaking.)
|
|
@~ This holds for commits too: since a commit has the SHA1 of the tree it
|
|
points to, then the commit SHA1 depends on the tree it points to. More
|
|
importantly, since a commit object has the SHA1 of its parent(s), then the
|
|
commit depends on them. This means that “replaying” a number of commits on
|
|
a different parent commit (eg, when doing a “rebase”) will always result in
|
|
a separate line of commit objects. These SHA1s are also global, meaning
|
|
that talking about a specific revision by its SHA1 will always refer to it
|
|
unambiguously (as long as others have that object in their repositories).
|
|
@~ By itself, this kind of storage @em{cannot} have any reference cycle. (At
|
|
least there is no practical way to get one.) The storage is therefore
|
|
inherently a DAG. In addition to this object store, git does have a number
|
|
of external references (eg, a branch is actually a pointer to a SHA1) — and
|
|
those could be arbitrary, but the object storage itself cannot have cycles.
|
|
@~ The fact that a commit has a pointer to a tree is what makes git keep
|
|
revisions of the whole tree — a commit cannot mark a change to a subtree.
|
|
(At least not with the usual higher-level commands that git implements.)}
|
|
@p*{
|
|
On top of this object store, there is a layer of meta-information about it.
|
|
The most important component here are branches (and tags). A branch is
|
|
basically a file that has the SHA1 of a specific commit (for example, your
|
|
@cmd{master} branch is a SHA1 that is stored in
|
|
@path{.git/refs/heads/master}). This is what makes branch creation extremely
|
|
cheap: all you need to do is create a new file with the SHA1.
|
|
@~
|
|
In addition, the @cmd{HEAD} (where your working directory is currently), will
|
|
usually have a symbolic reference rather than a SHA1 (you can see this
|
|
symbolic reference in the @path{.git/HEAD} file, which should usually look
|
|
like @cmd{ref: refs/heads/@i{branch-name}}). When you commit a new version,
|
|
a new commit object is created, and the branch that the @cmd{HEAD} points to
|
|
is updated. It is also possible to checkout a specific SHA1 of a commit
|
|
directly — the result of this is called “detached HEAD”, since the HEAD is
|
|
not a symbolic reference. The possible danger in doing this is that @cmd{git
|
|
commit} will create new commits that are derived from the one you're on, but
|
|
no branch is updated; if you later checkout something else, no reference is
|
|
left to your new commit which means that it could be lost now. For this
|
|
reason, if you checkout a SHA1 directly, git will spit out a detailed
|
|
warning, including instructions on how you could name your current position
|
|
(create a branch that points there).
|
|
@~
|
|
Tags come in two flavors: lightweight tags are SHA1 pointers like branches.
|
|
The problem with this is that such a tag could easily move to a different
|
|
commit, which is considered bad practice. For this reason, there are also
|
|
“annotated tags”, which are tag objects that are created in the object store.
|
|
These tags contain information that is similar to a commit (there's the
|
|
tagger's identity, the commit that it points to, and a log message) — and
|
|
they are reliable since you can refer to their SHA1. In this case, the
|
|
symbolic reference for such a tag (its name) will point to the tag object in
|
|
the store (it is also possible to move it, but that would also be bad
|
|
practice). Furthermore, tags (of both kinds) can point to any object in the
|
|
store — they can point to a tree or even to a specific blob. This is
|
|
sometimes used to store meta-information (eg, web pages) inside the
|
|
repository. (The repository for git itself has a tag that points to a blob
|
|
holding the maintainer's GPG key.)
|
|
@~
|
|
Note that all of this is under a more high level of managing information
|
|
between branches and repositories, with push/pull being the main operations
|
|
at that level. A high-level overview (more below):
|
|
@ul*{
|
|
@~ a branch is a line of development, represented as a pointer to the commit
|
|
at its tip;
|
|
@~ branches can be organized into hierarchies using @path{/} as a separator;
|
|
@~ some branches are local, and some are remote — remote ones are named
|
|
@path{remotes/origin/@i{branch}};
|
|
@~ local branches are represented as files in
|
|
@path{.git/refs/heads/@i{branch}} and remote ones are in
|
|
@path{.git/refs/remotes/origin/@i{branch}};
|
|
@~ @cmd{origin} is just the conventional name for the original repository you
|
|
cloned — later on you can add new remote repositories so you can push and
|
|
pull to/from them conveniently;
|
|
@~ some local branches are set to track remote ones, usually (but not
|
|
necessarily) the two will have the same name;
|
|
@~ you can also have local branches to track other local branches (with
|
|
pushing and pulling happening inside your repository);
|
|
@~ @cmd{git fetch} is used to update your remote branches — ie, connect to
|
|
the remote repository, get new commits (and the required parents and
|
|
trees), and update your remote branch with the new tips;
|
|
@~ @cmd{git merge} and @cmd{git rebase} are used to update one branch with
|
|
commits on another;
|
|
@~ @cmd{git pull} is, roughly speaking, a convenient way to do a fetch
|
|
followed by a merge (or a rebase, when used with @cmd{--rebase}).}
|
|
@~
|
|
There are several git tools that are relevant here. These are @em{not}
|
|
commands that you need to know for everyday use — so you can ignore this
|
|
part. It's only relevant if you want to see more of the low level structure
|
|
(or maybe if you want to write code that interfaces with a repository at this
|
|
level).}
|
|
@dl*{
|
|
@~ @cmd{git show @i{SHA1}}
|
|
@~ Show the object, in some appropriate way based on the type of the object.
|
|
(For blobs it shows the contents, for trees you get a listing of its
|
|
contents, and for commits it shows the log and the patch.)
|
|
@~ @cmd{git cat-file {-t | -s | @i{type} | -p} @i{SHA1}}
|
|
@~ A more low-level command that tells you the type/size of an object (@cmd{-t}
|
|
and @cmd{-s}), or shows the contents of an object as-is when given a type.
|
|
@cmd{-p} will “pretty-print” the object, eg, showing the contents of a tree
|
|
object instead of dumping its binary encoding.
|
|
@~ @cmd{git gc}
|
|
@~ Starts from a rootset holding all known references (branches, tags, etc),
|
|
and collects dangling objects. Such objects are generated due to various
|
|
reasons — for example, rebasing means that new commits are generated, and
|
|
the old ones are kept around. Actually, this will not remove recently
|
|
referenced objects — there is a protection mechanism that keeps them around
|
|
for a while, so if you somehow mess things up there is still a way to
|
|
recover.
|
|
@~ @cmd{git fsck}
|
|
@~ Does a “file system check” on the repository.
|
|
@~ @cmd{git rev-parse @i{symbolic-name}}
|
|
@~ Prints out the full SHA1 of a symbolic name (eg, a branch name or a tag
|
|
name). Will also print out the SHA1 given a possibly short prefix of one.
|
|
(Actually, this command can also show other information about a repository,
|
|
which makes it an important entry point for programs that deal with a
|
|
repository.)}
|
|
|
|
@section{Clone the PLT repository}
|
|
@p*{
|
|
As you probably know by now, in git you don't checkout a repository — you
|
|
clone it, getting a copy of the complete repository you cloned. This
|
|
includes the object store and the various references (branches and tags).
|
|
There are several ways to get the PLT repository, but the one that is
|
|
relevant to work on it is to do so through ssh — since this allows pushing
|
|
changes back to the server. (It is also possible to clone from one place and
|
|
push to another, but if you start with cloning through ssh your clone will be
|
|
already set up to push changes back.) The information that gitolite gives
|
|
you (with @cmd{ssh pltgit info}, assuming the above ssh setup) includes two
|
|
repositories that you have write access to: @cmd{plt} is the main repository,
|
|
and @cmd{play} is setup similarly (intended to try things out, see the
|
|
“Fooling around” section below). To get the main repository, run
|
|
@pre{git clone pltgit:plt}
|
|
which will create a @path{plt} directory with your new clone. You can now
|
|
start working in this directory.
|
|
@~
|
|
The repository is also available from other sources, some can be used for
|
|
read-only cloning:
|
|
@ul*{
|
|
@~ @cmd{git clone git://@|git-host|/plt.git}@br
|
|
cloning the repository using git's own network protocol
|
|
@~ @cmd{git clone http://@|git-host|/plt.git}@br
|
|
clone the repository over http
|
|
@~ @cmd{git clone http://github.com/plt/racket.git}@br
|
|
this uses the repository mirror on github, which is automatically kept in
|
|
sync (you can also use @cmd{https://...})}
|
|
and some present a web interface for additional information:
|
|
@ul*{
|
|
@~ @cmd{@selflink{http://@|git-host|/plt}}@br
|
|
a web interface to inspect the repository
|
|
@~ @cmd{@selflink{https://github.com/plt/racket}}@br
|
|
github's fancier web interface}}
|
|
|
|
@section{Start working: git commits vs subversion commits}
|
|
@p*{
|
|
As seen in the previous section, you start with
|
|
@pre{git clone pltgit:plt@";" cd plt}
|
|
And now you get to actually do some work.
|
|
@~
|
|
For the normal cycle of operations, working with git is not all that
|
|
different from working with subversion — you would change some files, and
|
|
then:
|
|
@pre{git commit some/paths}
|
|
or
|
|
@pre{git commit some/paths -m "add some feature" -m "requires another"}
|
|
only now the commit lives only in @strong{your clone only}, not in the server
|
|
(which is why committing is blindingly fast, not requiring a network
|
|
connection). To push your commits to the server, run @cmd{git push}, and to
|
|
pull updates from the server run @cmd{git pull}. This is obviously very much
|
|
oversimplifying the process: mainly neglecting to talk about updates on the
|
|
server when you already have local changes. (See below for a more detailed
|
|
explanation.) Note that in these examples I'm explicitly specifying the
|
|
paths to commit, either the files that you want to commit or a directory
|
|
where you want to commit all changes. See the section below on the “staging
|
|
area” for more details.
|
|
@~
|
|
One major difference to keep in mind is that git commits are @strong{not}
|
|
like subversion commits. (This is confusing since many places that discuss
|
|
the difference between the two and/or try to teach git to subversion users
|
|
almost always work under the assumption that commits in the two systems are
|
|
the same.) The thing is that git commits are done at a finer level than
|
|
subversion commits — since a commit is done locally and not on the server.
|
|
To really imitate how subversion works, you would push all commits right
|
|
after you create them — essentially equating commits with pushes, which is
|
|
how you work with a subversion repository. But by just @em{not} doing this,
|
|
you will immediately get some of the benefits that git gives you. So a
|
|
better way to think about it is: in git you commit at points that make sense
|
|
for the respective changes, usually at a finer level than subversion commits.
|
|
Then, you push back a bunch of commits to the server — whether one or a
|
|
hundred. The point where you push your changes to the server is effectively
|
|
the point where you decide that you're in a good enough state to make your
|
|
work public.
|
|
@~
|
|
Incidentally, following this intuition, drdr is running a build for every
|
|
push to the server — not for every commit. When you push to the server, it
|
|
will tell you which push number this is — these numbers are going to be used
|
|
by drdr, and they (very!) roughly correspond to subversion commits.
|
|
(Currently, every push gets a number, but in the future this might be used
|
|
only for pushes to the master branch.) There's no plan at the moment to use
|
|
these numbers for anything else.}
|
|
|
|
@section{Fooling around with git}
|
|
@p*{
|
|
Experimenting with git is easy to do, and the server is set up to make it
|
|
even easier. You can use one of the following ways to experiment safely with
|
|
the main repository:
|
|
@ul*{
|
|
@~ There is a @cmd{play} repository on the server. This repository is very
|
|
similar to the @cmd{plt} repository, and it is set up in the same way that
|
|
@cmd{plt} is. Feel free to destroy it in any way you want, even if it
|
|
becomes unusable, it's easy to just recreate it.
|
|
@~ You can create (and later delete) your own repositories — including making
|
|
your own copy of the main repository, an operation that is known as
|
|
“fork”. Your fork will be created efficiently (ie, creating a fork of the
|
|
@cmd{plt} repository is cheap), but any changes made to it will not affect
|
|
the main repository. A fork is created with a gitolite command, and once
|
|
it's there you can clone it and eventually delete it. Here are the
|
|
relevant commands — use your actual username in place of @cmd{$user} (or
|
|
have @cmd{$user} set to your username):
|
|
@ol*{@~ @cmd{ssh pltgit fork plt $user/myplt}
|
|
@~ @cmd{git clone pltgit:$user/myplt}
|
|
@~ ...play with this clone, push, pull, etc...
|
|
@~ @cmd{ssh pltgit delete $user/myplt}}
|
|
More on user repositories below.}}
|
|
|
|
@section{The staging area}
|
|
@p*{
|
|
Something that tends to confuse people is git's “staging area”. This is a
|
|
concept that is unique to git — roughly speaking, you can have three versions
|
|
of a tree:
|
|
@ul*{
|
|
@~ the files that you actually see (and edit) — the working directory,
|
|
@~ another is the staging area which you add stuff to from the working tree
|
|
using @cmd{git add},
|
|
@~ and then there is the tree that is in the HEAD with all prior versions.}
|
|
@~
|
|
The thing that can confuse here is that when you @cmd{git add some/file} for
|
|
a file that you edited (or created) and then edit it further, then the
|
|
version will get committed by a plain @cmd{git commit} will be the one that
|
|
was added. Note that @cmd{git status} will tell you which modifications are
|
|
in the staging area waiting to be committed, and which modifications are in
|
|
your working directory — in the given example, it will tell you that
|
|
@path{some/file} is in both.
|
|
@~
|
|
The staging area can be useful at times, but most likely at the beginning
|
|
stages you will want to just avoid it. The good news is that it is easy to
|
|
do so.}
|
|
@dl*{
|
|
@~ @strong{Avoid the @cmd{-a} flag}.
|
|
@~ @p*{
|
|
Before we see how to ignore it, note that there are many web pages that
|
|
will tell you to use @cmd{-a} with the commit command. This will make git
|
|
commit all changes to tracked files — @strong{including tracked files that
|
|
are outside of your current directory}, and this can make you commit
|
|
changes that you didn't intend to commit.}
|
|
@~ @strong{Always specify a path to @cmd{git commit}}.
|
|
@~ @p*{
|
|
The easiest way to avoid the staging area is to specify the path(s) to
|
|
what you want to commit, possibly @path{.} for all changes in the current
|
|
directory (and below). Specifying a path this way will make
|
|
@cmd{git commit} behave very similarly to subversion: tracked files that
|
|
were modified will get committed, and added files (with @cmd{git add})
|
|
that are listed in the paths-to-be-committed are also committed. Tracked
|
|
and added files that are not listed (and not in a specified subdirectory)
|
|
are left as-is. So, if you had a habit of doing this with subversion
|
|
(@cmd{svn commit .}), then git will essentially do the same. You will
|
|
still need to use @cmd{git add} for newly created files, but this is
|
|
essentially the same as with subversion.
|
|
@~
|
|
It is also possible to “make up new git commands” for yourself. See the
|
|
following section on the subject: it adds a new @cmd{git ci} command that
|
|
passes @path{.} to @cmd{git commit}, similarly to what @cmd{svn ci} does
|
|
by default.}
|
|
@~ It is a good idea to @strong{avoid using the @cmd{-m} flag}, until you're
|
|
more comfortable with git.
|
|
@~ @p*{
|
|
Let git pop up an editor to write the commit log: the file that you will
|
|
edit will list the changes that you are about to commit as well as changes
|
|
that you are not going to commit. Glancing through it, you will see
|
|
changes that you missed, furthermore, the paths are relative which makes
|
|
it easy to quickly distinguish paths in the current directory and outside
|
|
of it (the latter will begin with @path{../}). If you see any problems,
|
|
just make sure that you have no commit message in the editor and when you
|
|
quit it git will abort the commit (same as subversion).}
|
|
@~ @strong{Don't push out all commits to the server immediately}.
|
|
@~ @p*{
|
|
Even if you did commit something by mistake, it is possible to undo the
|
|
commit — run @cmd{git reset HEAD^}, which will undo the last commit (it
|
|
moves the branch to the parent commit), and the changes that are no longer
|
|
committed will be left in your working directory, so you get to try the
|
|
commit again. Note that this is possible @strong{only if you didn't push
|
|
out the commit} that you're undoing — if you did, then the server will
|
|
later not allow you to push changes that are not strict extensions of what
|
|
it has (since this is likely to confuse other people who already got your
|
|
commit).
|
|
@~
|
|
So in general, remember that you can commit often, and commit when it
|
|
makes sense to do so, and push commits out only when you're done with
|
|
whatever you were working on. Consider your local git history as
|
|
something that you have full control over: you can undo commits and redo
|
|
them (in fact, @cmd{git commit --amend} does just that: undo the last
|
|
commit, and combine it with new changes — it's a solution for “oops
|
|
commits”), you can rebase them, and you can just throw away everything and
|
|
start from scratch. But when you do push your history out, the party is
|
|
over, and any mistakes will need to be rectified in further commits (eg,
|
|
you can no longer use that @cmd{--amend} flag, you have to do an “oops
|
|
commit”).
|
|
@~
|
|
(BTW, strictly speaking, it is only the policy on our server that forbids
|
|
such rewritten history — since this is likely to be a mistake for now, and
|
|
if it happens most people will be confused about what needs to be done.)
|
|
@~
|
|
Also, as said above, pushing all commits immediately means that you're
|
|
essentially restricting yourself to the same mode of operation as
|
|
subversion. Same mode, but more complicated — and you won't enjoy any of
|
|
the benefits, which will guarantee that you will suffer.}}
|
|
|
|
@section{Configuring and extending git}
|
|
@p*{
|
|
As mentioned above, git uses several configuration files that customize
|
|
various aspects. The two important ones are your global file
|
|
(@path{~/.gitconfig}) and a per-repository file in @path{.git/config} at the
|
|
repository root — whenever a value is needed, git will first consult the
|
|
repository configuration, and if the option is not set there it will try your
|
|
global version. (It will also look at a system-wide configuration, but this
|
|
is irrelevant here.) Configuration options names are separated by a
|
|
@path{.}, and configuration files have a simple syntax, with @cmd{foo.bar}
|
|
option listed as a @cmd{bar = some value} line in a @cmd{[foo]} section.
|
|
Note that you can set @em{any} configuration you want to, no restrictions.
|
|
This can be useful for customizing various extensions, including scripts that
|
|
you may want to write (for example, the git server has a script that checks
|
|
the @cmd{hooks.counter} option to know if it should keep track of pushes).
|
|
This is facilitated by several options to @cmd{git config} which makes it
|
|
easy to query configurations from scripts etc.
|
|
@~
|
|
To edit your configuration options you can either use the @cmd{git config}
|
|
command, or you can edit the file directly. When @cmd{git config} changes
|
|
the file it rewrites only part of the file and leaves the rest untouched,
|
|
which conveniently leaves your own format and any comments you might want to
|
|
include. To set a value through the command and then get it:
|
|
@pre{git config foo.bar some-value
|
|
git config foo.bar}
|
|
and you can add a @cmd{--global} flag to either form to use only your global
|
|
configuration file. There are many other options — for dealing with keys
|
|
that must be booleans or integers, for keys with multiple values, etc. The
|
|
@man{git-config} man page will tell you much more on this.
|
|
@~
|
|
The man page also lists the configuration options that customize various
|
|
functionalities. Here are some important ones that you should consider
|
|
setting (each listed as a command that sets it globally):
|
|
@ul*{
|
|
@~ @npre{git config --global user.name "My Name"
|
|
git config --global user.email "foo@at-git-racket"}
|
|
@p*{
|
|
As said in the beginning of this text, you will likely want to set a
|
|
default username and email for yourself. But note that if you do set
|
|
this globally, it will be your default identity for all repositories.
|
|
This makes sense only if you commit to PLT-related repositories, but it
|
|
can be confusing if you're also committing to some other non-PLT-related
|
|
repositories and want to commit under a different email (or name) — for
|
|
example, you may want to commit to a public project with a gmail
|
|
address, and to a departmental repository with your
|
|
@cmd|{foo@cs.bar.edu}| email.
|
|
@~
|
|
You could set the racket-lang.org identity locally in your PLT clone or
|
|
you could set your other identity in the other repository, but in any
|
|
case you should be aware of this and avoid letting git guess your name
|
|
and email. (Some confusion is likely to happen anyway, and git has a
|
|
way to “map” some name/email to another when mistakes happen.)}
|
|
@~ @npre{git config --global push.default tracking}
|
|
@p*{
|
|
By default, when you run @cmd{git push}, git will push all branches that
|
|
correspond to branches in the remote repository. This can be surprising
|
|
if you're working on several branches since it will push them all out.
|
|
Setting this option to @cmd{tracking} will make git push the current
|
|
branch to the branch it is tracking.
|
|
@~
|
|
Another option for this is @cmd{current}, which makes @cmd{git push}
|
|
always push the current branch to the remote it was cloned from. This
|
|
is convenient in that you never need to set up how local branches track
|
|
remote ones — it's as if all local branches @em{always} track all remote
|
|
branches under the same name. For example, after you clone an empty
|
|
repository (see the user repositories section below), a @cmd{git push}
|
|
will push a master branch remotely — whereas with @cmd{tracking} you
|
|
need to have the first push explicitly specify the branch to push,
|
|
usually @cmd{git push origin master} (this sets things up so later you
|
|
can just run @cmd{git push}). However, using @cmd{current} you can no
|
|
longer push from one local branch to another local branch it is set to
|
|
track.
|
|
@~
|
|
So a possible conclusion here is that you should use @cmd{tracking},
|
|
unless you plan on branches to always track remote branches by the same
|
|
name. @cmd{tracking} is often preferred over @cmd{current}.}
|
|
@~ @npre{git config --global core.excludesfile "~/.gitignore"}
|
|
@p*{
|
|
You'll probably want to always ignore a number of common patterns, like
|
|
backup files or OSX @path{.DS_Store} files. To do this, you first set a
|
|
default file as shown here (note that @path{~} is quoted, and git will
|
|
expand it to whatever your home directory is). If you have this
|
|
setting, you can then create a file at this path with patterns for files
|
|
that you always want to ignore. This file has shell-patterns (and
|
|
possibly @cmd{#}-comments) — for example:
|
|
@pre{# backups
|
|
*~
|
|
# autosaves (note the #-quoting)
|
|
\#*
|
|
# OSX junk
|
|
.DS_Store}
|
|
(See the @man{gitignore} man page for a few more details.)
|
|
@~
|
|
In addition to this, git repositories can have their own
|
|
@path{.gitignore} files (unlike @path{.git/config} files), which are
|
|
combined hierarchically together with this global option. In fact, you
|
|
don't really need to set the above ignores for the PLT repository since
|
|
they're already included in its toplevel @path{.gitignore} file — but
|
|
doing so is still a good idea since you're likely to work on other
|
|
repositories too.}
|
|
@~ @npre{git config --global core.editor emacs
|
|
git config --global core.pager less}
|
|
@p*{
|
|
These two settings are used to tell git which command to use for editing
|
|
log messages, and which command is used to paginate output. (The former
|
|
might already be set in your environment as the value of the
|
|
@cmd{EDITOR} variable.) If you set the latter to @cmd{cat}, git will
|
|
just spill all output directly. In addition to these, you can also
|
|
control which individual commands use a pager, for example, to disable
|
|
the pager for @cmd{git log}, you can do this:
|
|
@pre{git config --global pager.log false}}
|
|
@~ @npre{git config --global color.ui auto
|
|
@i{...}
|
|
git config --global color.branch.current yellow red bold
|
|
git config --global color.branch.local yellow
|
|
git config --global color.branch.remote green
|
|
@i{...}}
|
|
@p*{
|
|
These settings control how git uses colors: whether it shows them, and
|
|
which colors it will use for various outputs. There are many of these
|
|
settings, which you can find in the @man{git-config} man page.}
|
|
@~ @p*{
|
|
@cmd{remote.origin.*}, @cmd{branch.master.*}
|
|
@~
|
|
Git keeps track of what the @cmd{origin} repository and how branches
|
|
track other branches in the configuration file too. (You will have such
|
|
entries for all known remote repositories and branches.) Usually you
|
|
set these values (often implicitly) via various git commands — but you
|
|
might want to look in your configuration file if you want to tweak
|
|
things yourself. Note that for configuration names with more than two
|
|
parts, the section name will something like @cmd{[remote "origin"]}.}
|
|
@~ @p*{
|
|
@cmd{sendemail.identity}, @cmd{sendemail.from},
|
|
@cmd{sendemail.bcc}, @cmd{sendemail.suppresscc}, ...
|
|
@~
|
|
These settings configure @cmd{git send-email}, which is used to send
|
|
patches from your repository elsewhere. You will probably want to
|
|
customize them if/when you get to use this facility often. (See
|
|
below.)}}
|
|
@~
|
|
In addition to these settings, you can extend git with your own aliases and
|
|
commands. Aliases are stored in your git configuration — so you can use
|
|
@cmd{git config} to create an alias, for example, @pre{git config --global
|
|
alias.up "pull --stat --all"} creates a global @cmd{git up} command which is
|
|
actually a short alias for running @cmd{git pull --stat --all}.
|
|
@~
|
|
@strong{Notes about aliases:}
|
|
@ul*{
|
|
@~ To edit aliases, it is more convenient to edit your configuration file
|
|
directly.
|
|
@~ Since aliases are stored in git configuration files, they can be made
|
|
local to each repository.
|
|
@~ When command-line arguments are given to the alias, they will be appended
|
|
to the alias text.
|
|
@~ Aliases @em{cannot} override git commands; this is intentional, to avoid
|
|
scripts breaking due to modified commands.
|
|
@~ An alias that starts with a @cmd{!} character will be run as a shell
|
|
command. For example, you can use
|
|
@pre{k = "!gitk -d"}
|
|
to make @cmd{git k} run the gitk program with the @cmd{-d} flag.
|
|
@~ Some aliases that I find useful are:
|
|
@pre{# satisfy the "up instinct"
|
|
up = pull --ff-only --stat --all
|
|
# quick status, similar to what subversion shows
|
|
st = status -s
|
|
# we will be dealing more with branches
|
|
br = branch}}
|
|
@~
|
|
In addition to aliases, you can create new git commands using a script that
|
|
is called @cmd{git-@i{something}} somewhere in your path. (Note that these
|
|
cannot override known git commands either.) Such commands will be available
|
|
as @cmd{git @i{something}}. One use for this is using our facility for
|
|
managing file properties — the @path{collects/meta/props} program. To do
|
|
this put this in a file called @cmd{git-prop} somewhere in your PATH, for
|
|
example, @path{~/bin/git-prop}:
|
|
@pre|{#!/bin/sh
|
|
top="$(git rev-parse --show-toplevel)" || exit 1
|
|
exec "$top/collects/meta/props" "$@"}|
|
|
then run @cmd{chmod +x ~/bin/git-prop}, and you can now use it as a git
|
|
command (try @cmd{git prop -h}). Note the use of the @cmd{rev-parse}
|
|
command: it will display the repository root, which means that you will get
|
|
to run the props script of the repository you're @em{currently} using.
|
|
(There are many git commands that are useful for such scripts.)
|
|
@~
|
|
Another useful script is @cmd{git-ci} which mimics the behavior of
|
|
@cmd{svn commit} (avoiding confusion with the staging area). As said above,
|
|
a good way to achieve this is to specify the current directory (@path{.}) if
|
|
you don't specify any other path. If you save this as @path{git-ci}, you
|
|
will get a @cmd{git ci} that does just that:
|
|
@pre|{
|
|
#!/bin/sh
|
|
add_dot=yes; for p; do if [ -e "$p" ]; then add_dot=no; fi; done
|
|
if [ -e "$(git rev-parse --git-dir)/MERGE_HEAD" ]; then add_dot=no; fi
|
|
if [ -d "$(git rev-parse --git-dir)/rebase-apply" ]; then add_dot=no; fi
|
|
if [ $add_dot = yes ]; then git commit . "$@"; else git commit "$@"; fi
|
|
}|
|
|
This small script will basically check all arguments and see if one is an
|
|
existing path (or if you're resolving a merge). If none are, it adds
|
|
@path{.} as a first argument (this avoids confusing it as a value for some
|
|
flag). Note that this is not completely foolproof: for example, if you'll
|
|
use the @cmd{-m .} hack, it will assume that you did specify a path. (But
|
|
you should really avoid such log messages.)}
|
|
|
|
@section{User repositories}
|
|
@p*{
|
|
As mentioned above, the PLT server allows you to create your own
|
|
repositories. Repositories on the server can be organized in a nested
|
|
directory structure, and you “own” all repositories that are in a directory
|
|
with your username. The gitolite @cmd{info} command that was mentioned above
|
|
shows you this with a @cmd{C CREATER/.*} line: this means that you can create
|
|
any repository if it is in a subdirectory with your username. (In this
|
|
discussion, “the server” is actually the gitolite script that runs on the
|
|
server.)
|
|
@~
|
|
Any git operation that you do on a repository that you own which does not
|
|
exist will make the server create it for you — for example, if you clone such
|
|
a repository. To run these examples use your git username instead of
|
|
@cmd{$user}, or simply set the @cmd{user} variable in your shell (as this
|
|
example shows) — this is only to make copy-pasting easy, of course.
|
|
@pre{user=eli # your own username here
|
|
git clone pltgit:$user/foo}
|
|
(You are encouraged to run these commands — at the end of this section you'll
|
|
see how you can clean things up.)
|
|
@~
|
|
What will happen now is (a) git will initialize a @path{foo} repository for
|
|
you, (b) it will connect to the server to clone its contents, (c) the server
|
|
will notice that it doesn't exist so it will create it, (d) your git process
|
|
will clone the empty result. Because the remote repository is empty, git
|
|
will complain that “You appear to have cloned an empty repository” — this is
|
|
expected, so you shouldn't worry about it. Once you have your (empty) clone,
|
|
you can populate it as usual, then push the new content back to the server's
|
|
copy:
|
|
@pre{cd foo
|
|
@i{...create some files...}
|
|
git add .
|
|
git commit -m "initial content"
|
|
git push origin master}
|
|
Note that the last command explicitly names the branch to push over — once
|
|
this push is done, git will remember this relation and further pushes can be
|
|
done with just @cmd{git push}. If you happen to forget this and use
|
|
@cmd{git push}, then git will not push anything, and it will tell you about
|
|
it and suggest specifying a branch. On the other hand, if you set the
|
|
@cmd{push.default} configuration option to @cmd{current} (as described in the
|
|
customization section above), then even in this first push you can just run
|
|
@cmd{git push} since git assumes that you always want local branches to
|
|
correspond to remote branches by the same name.
|
|
@~
|
|
Instead of cloning a repository to create a new one, you could also start
|
|
with an existing repository and simply push it to a yet-to-exist repository
|
|
on the server. Again, the server will see that it doesn't exist and will
|
|
create it for you (provided that it is in your directory). To continue the
|
|
above example, I could now create a new repository from @cmd{foo}:
|
|
@pre{# (still in the foo directory)
|
|
git push pltgit:$user/bar}
|
|
@~
|
|
There is, however, an issue of efficiency here: with this last command I just
|
|
created a second copy of it all. This could be problematic if you have a
|
|
large repository (eg, a copy of the @cmd{plt} repository). (Note that with
|
|
subversion this is the only way to do things, but there you would create
|
|
copies inside the tree, which subversion optimizes.) One nice feature of git
|
|
is that creating a clone of a repository on the same filesystem will use
|
|
hard-links for the clone, which makes the clone use very little additional
|
|
space. But the problem is that you have no access to the PLT server. The
|
|
solution here is in the form of a gitolite @cmd{fork} command (this is
|
|
actually our own extension) — this command will create a clone on the server,
|
|
starting from a specified repository. I could therefore create my @path{bar}
|
|
repository as a copy of @path{foo} with the following:
|
|
@pre{ssh pltgit fork $user/foo $user/bar}
|
|
(Note that if you follow these examples and you already have @path{bar}, the
|
|
server will tell you about it.) The result is a @path{$user/bar} repository
|
|
that was cloned from @path{$user/foo}, and the two share their store using
|
|
hard links. If the two repositories are updated with identical content, the
|
|
new content will not be shared, but for a large repository like the @cmd{plt}
|
|
repository you still get the benefit of having the bulk of the data shared
|
|
(the complete store, at the time of forking). To get a feeling for how fast
|
|
this is, you can now clone the @cmd{plt} repository to your own private copy:
|
|
@pre{ssh pltgit fork plt $user/myplt}
|
|
This would seem suspiciously fast for such a large repository — but this
|
|
repository has most of the data packed (objects in the store are put in large
|
|
“pack files”), so there are not too many files, and the server-side cloning
|
|
basically created hard links to these files. The result is fast, efficient
|
|
(even in speed: when you interact with your clone, files are likely to be
|
|
paged in memory), and cheap.
|
|
@~
|
|
As we've seen above, the gitolite @cmd{info} command lists the permissions
|
|
that you have, but it doesn't show you the actual repositories. For this,
|
|
there is an @cmd{expand} command. (Yes, this is not a great name; it's
|
|
related to how gitolite was intended to be used. Remember that there is also
|
|
a @cmd{help} command that describes the available gitolite commands.) When
|
|
you run the expand command — @cmd{ssh pltgit expand} — you get a listing of
|
|
all of the repositories that you can access, each with an indication of read
|
|
permissions (@cmd{R}) or write permission (@cmd{W}). A @cmd|{@}| indicator
|
|
means that you have the respective permissions because it is allowed for all
|
|
users. Each repository is also listed with its owner, or @cmd{<gitolite>} in
|
|
case it is a globally configured repository.
|
|
@~
|
|
Some of the gitolite commands are used to configure your repositories — you
|
|
can only use these with repositories that you own.
|
|
@ul*{
|
|
@~ @cmd{getperms} and @cmd{setperms} — these are used to get or set
|
|
permissions for your repositories. The first will print the current
|
|
permissions (which will be initially empty), and the second will read the
|
|
permissions on its input and set them. The format of the permissions is
|
|
simple: each line begins with an @cmd{R} or @cmd{RW}, and then the
|
|
usernames that this permission applies to. You can use the magic username
|
|
@cmd|{@all}| to grant access to everyone in the system. For example, to
|
|
grant read permissions to everyone, and write permissions to user1, create
|
|
a file with:
|
|
@pre|{R @all
|
|
RW user1 user2}|
|
|
and then run the setperms commands with this as its input:
|
|
@pre{ssh pltgit setperms $user/foo < the-file}
|
|
You can also just run the @cmd{setperms} command and type in the
|
|
permissions directly. Note that these permissions are not cumulative:
|
|
every use of @cmd{setperms} specifies all permissions. (We might have a
|
|
more convenient interface for all of this in the future.)
|
|
@~ @cmd{config} — this command can be used to set known configuration options
|
|
in your repositories. It works with sub-verbs:
|
|
@dl*{@~ @cmd{ssh pltgit config list}
|
|
@~ Displays the known configuration options
|
|
@~ @cmd{ssh pltgit config get @i{repo} @i{config}}
|
|
@~ Displays the configuration value of a specific repository
|
|
@~ @cmd{ssh pltgit config set @i{repo} @i{config} @i{value}}
|
|
@~ Sets the configuration value of a specific repository}
|
|
These configuration options can customize aspects of the scripts that run
|
|
after every push — currently, you can use this to set an email address to
|
|
send notification emails to. Other configurations may be added in the
|
|
future. (Note that this does not let you set any configuration, since
|
|
some of these can execute arbitrary commands.)
|
|
@~ @cmd{delete} — finally, this command can be used to delete repositories.
|
|
For example, to clean up the above, you can now run:
|
|
@pre{ssh pltgit delete $user/foo
|
|
ssh pltgit delete $user/bar}
|
|
The repositories are moved to a temporary holding directory, and will
|
|
eventually be removed. The bottom line here is that if you lost anything
|
|
by mistake recently, chances are there's a backup of your repository.}}
|
|
|
|
@section{Working with git}
|
|
|
|
@subsection[#:newpage? #f]{Basics}
|
|
@p*{
|
|
The above description is much simplified in that it doesn't deal with
|
|
development that happens outside of your own work — and such development
|
|
obviously changes the story. Overall, this is not too different from working
|
|
with subversion: if there were any changes on the server you need to update
|
|
your working copy first, and this implies dealing with conflicts if there are
|
|
any. But the way to deal with such things in git is significantly different
|
|
than dealing with them in subversion, and this difference is at the technical
|
|
level (different commands) and at the workflow level (you will likely branch
|
|
much more, and you're likely to push less frequently than you commit with
|
|
subversion).
|
|
@~
|
|
To start working, you first need to get a repository clone. Usually you will
|
|
clone the PLT repository (or a private copy that you do your work in), but
|
|
remember that to experiment with git you have the @cmd{play} repository or
|
|
you could make a fork of the PLT repository to play with and remove it when
|
|
you're done. Either way, be sure to try these things out — it will make your
|
|
life much easier in the future.
|
|
@~
|
|
In the following examples I will use an empty repository to demonstrate
|
|
things and I will list the exact commands that I'm using (this means that I
|
|
will use unix commands to create and edit files, and use @cmd{-m} when
|
|
committing). Lines that I enter are displayed with a @cmd{$} prompt, most
|
|
output lines are omitted, comments start with @cmd{#}, and @cmd{$user} is set
|
|
to my username. Note that if you try this yourself, the SHA1s of commits
|
|
will be different (the reason for that is that a commit object includes the
|
|
author name and email and the date). Note also that in some places I will
|
|
“jump to an earlier continuation”: start from an earlier state and do
|
|
something different — so if you want to try these things out it will be
|
|
convenient to put the commands in a shell script so you can re-run it to get
|
|
to the earlier state.
|
|
@~
|
|
First, I create a private empty repository, populate it, and update the
|
|
remote repository:
|
|
@pre{$ user=eli # your own username here
|
|
$ mkdir /tmp/sandbox; cd /tmp/sandbox
|
|
$ ssh pltgit delete $user/foo # delete previous repository, if any
|
|
$ git clone pltgit:$user/foo
|
|
$ cd foo
|
|
$ echo "foo" > foo; echo "bar" > bar
|
|
$ git add .
|
|
$ git st # uses the `st' alias as shown above
|
|
A bar
|
|
A foo
|
|
$ git commit -m "initial content" .
|
|
[master (root-commit) 87f1f02] initial content
|
|
@i{...}
|
|
# git tells us the branch we committed to, the new commit SHA1 and
|
|
# that this is the first commit, and the log message; we can verify
|
|
# this now with `git log'
|
|
$ git log
|
|
commit 87f1f02c23b32e7f9b... # this is the commit object I created
|
|
@i{...}
|
|
$ git push # (or `git push origin master' if needed)
|
|
To pltgit:eli/foo # where we pushed to, and the branch
|
|
* [new branch] master -> master}
|
|
@~
|
|
@small{[A quick note on commit messages: several git command consider the
|
|
first paragraph of your commit message as a short description for it. This
|
|
is all of the text up to the first blank line if you write a commit message
|
|
in an editor, or the first @cmd{-m} message if you use it with
|
|
@cmd{git commit} (it accepts multiple @cmd{-m} arguments, for multiple
|
|
paragraphs). Keep this in mind when composing such messages.]}
|
|
@~
|
|
To see what happens when multiple people commit to the repository, we create
|
|
a second clone of our repository now in a @path{foo2} directory:
|
|
@pre{$ cd ..
|
|
$ git clone pltgit:$user/foo foo2
|
|
$ cd foo # go back to foo now}
|
|
@~
|
|
Lets make two new commits now:
|
|
@pre{$ echo "more foo" >> foo; echo "more bar" >> bar
|
|
$ git ci -m "more stuff" # uses the `git-ci' script from above
|
|
[master b7d3c41] more stuff
|
|
$ echo "even more foo" >> foo
|
|
$ git ci -m "even more stuff"
|
|
[master 18bc0e6] even more stuff}
|
|
@~
|
|
At this point, instead of blindly pushing these commits, lets look around
|
|
first. One useful tool for inspecting the history is @cmd{gitk} — if you run
|
|
it now, you will see the simple 3-commit graph, and two of them are marked as
|
|
branches — clearly showing that your local master branch is 2 commits ahead
|
|
of the remote one. This could be different at this point: someone else might
|
|
have pushed more commits to the remote — remember that your remote master
|
|
branch (@cmd{remotes/origin/master}) is not really what's on the remote, but
|
|
rather what you know about it last time you pulled from it.
|
|
@~
|
|
Another useful command to examine the history is @cmd{git log}, which can
|
|
show commit history in many ways. As things stand in the current repository,
|
|
if you just run @cmd{git log}, you will see a listing of the same three
|
|
commits that gitk shows. To get a more condensed format with
|
|
one-line-per-commit, use @cmd{--oneline}. Another thing that you can do is
|
|
show a specific range of commits — you can do this by specifying two
|
|
revisions separated with @cmd{..}, where the revisions can be written
|
|
explicitly using the (short prefix) SHA1 form, or more conveniently using a
|
|
symbolic name (eg, branch, tag, HEAD):
|
|
@pre{$ git log origin/master..master}
|
|
@~
|
|
Since we @em{are} currently on the master branch, we could use @cmd{HEAD} for
|
|
the second one (@cmd{origin/master..HEAD}), but this is also the default, so
|
|
an even shorter form is @cmd{origin/master..}. In addition to @cmd{git log},
|
|
you can also use @cmd{git diff} in a similar way, but instead of a commit
|
|
listing, you get the diff between the two specified points, so
|
|
@pre{$ git diff origin/master..}
|
|
will show you the changes that you did not yet push. Note that there are a
|
|
number of places where git will guess the full name of branches, for example,
|
|
@cmd{origin/master} is actually a short name for @cmd{remotes/origin/master}.
|
|
In a similar way, just @cmd{origin} will make git guess that you're talking
|
|
about @cmd{origin/master}.
|
|
@~
|
|
In these cases, the revision specification for @cmd{log} and @cmd{diff} are
|
|
the same, but this is a little misleading: @cmd{git diff} usually works by
|
|
comparing two specific end points in your history, but @cmd{git log} actually
|
|
works on a @em{set of commits} rather than on a range. The @cmd{R1..R2}
|
|
notation is actually shorthand for @cmd{^R1 R2} — specifying a commit means
|
|
“the commit and all of its parents”, and a @cmd{^} prefix negates a set, so
|
|
@cmd{^R1 R2} means “include the set of commits leading to R2 (inclusive), but
|
|
exclude the ones leading to R1 (inclusive)”.
|
|
@~
|
|
In addition to this range/set specification, there is a lot to specifying a
|
|
revision set. As mentioned, you can use a SHA1 (or a shorter unique prefix),
|
|
or you can use a symbolic name. You can also use @cmd{R^} for the parent of
|
|
@cmd{R} (the first parent in the case of merge commits, which have more than
|
|
one parent), @cmd{R^^} would be the grand parent, @cmd{R~3} is the
|
|
3rd-generation parent commit. There is also @cmd|{R@{yesterday}}|,
|
|
@cmd|{R@{1 month 2 weeks go}}| etc for a symbolic name R — which refers to
|
|
the branch/HEAD at that point in time (this refers to @em{your own} version
|
|
of it at that time; there are @cmd{--since} and @cmd{--until} flags to filter
|
|
commits by the time they were made at). You can also use a -N flag (where N
|
|
is an integer) to show only N commits. Finally, you can use a branch name R
|
|
with @cmd|{R@{upstream}}| (short: @cmd|{R@{u}}|) to refer to the “upstream”
|
|
version of the branch — the branch that R is set to follow. This is
|
|
particularly convenient for things like @pre|{$ git log --oneline @{u}..}|
|
|
which will always show the commits that you have over the branch your current
|
|
one follows. (For example, you could set up an alias to use this.)
|
|
@~
|
|
We now continue by pushing our two commits to the remote server. Since we
|
|
already did a push, a plain @cmd{git push} works fine.
|
|
@pre{$ git push # no need to specify a branch now
|
|
To pltgit:eli/foo
|
|
87f1f02..18bc0e6 master -> master}
|
|
@~
|
|
As you can see in the last line, git tells us that we pushed from our local
|
|
@cmd{master} branch to the remote @cmd{master} branch, and that this made it
|
|
advance from the first commit we pushed (87f1f02) to the last one we created
|
|
now (18bc0e6).
|
|
@~
|
|
Since we've made some progress in one place, we can go to the @path{foo2}
|
|
clone to see what happens when we update a repository that did not have these
|
|
changes:
|
|
@pre{$ cd ../foo2
|
|
$ git pull
|
|
@i{...}
|
|
From pltgit:eli/foo
|
|
87f1f02..18bc0e6 master -> origin/master
|
|
Updating 87f1f02..18bc0e6
|
|
Fast-forward
|
|
bar | 1 +
|
|
foo | 2 ++
|
|
2 files changed, 3 insertions(+), 0 deletions(-)}
|
|
@~
|
|
This looks expected — git shows the new commits that we received, and they're
|
|
the same as what we pushed earlier. Using @cmd{git pull} is actually doing
|
|
two things: it's running @cmd{git fetch} first to update your remote branch
|
|
from the server, and then it uses @cmd{git merge} to merge it into your
|
|
master branch. The point where @cmd{get merge} starts is the “Updating” line
|
|
— and there's an important thing to note here: the next line says
|
|
“Fast-forward”, which is a special kind of a merge. When you merge some
|
|
branch into your branch, and this branch is a proper superset of your branch
|
|
(it has commits that your branch doesn't, and all commits in your branch are
|
|
included in it), git will simply “move your branch forward” to the other: it
|
|
will update your branch to the tip of the merged one, and then your working
|
|
directory will be update accordingly.
|
|
@~
|
|
It is often better to do the fetch first, so you can see the changes that
|
|
happened remotely before you merge them. To do this we're going to use
|
|
@cmd{git fetch}, avoiding the merge step that @cmd{git pull} does. In fact,
|
|
since creating a merge commit is something that you might want to always do,
|
|
@cmd{git pull --ff-only} will only do the merge if it will be a fast-forward
|
|
merge.
|
|
@~
|
|
Assuming we start again from the @path{foo2} repository before the above
|
|
pull, we get the same output up to the point where the merge started:
|
|
@pre|{$ git fetch
|
|
@i{...}
|
|
From pltgit:eli/foo
|
|
87f1f02..18bc0e6 master -> origin/master
|
|
$ git log --oneline @{u}..
|
|
# nothing
|
|
$ git log --oneline ..@{u}
|
|
# the same two commits}|
|
|
The first log doesn't show anything, since we have no commits over the ones
|
|
in the remote (the “upstream” of our current branch). To see this, consider
|
|
that after expanding empty names to @cmd{HEAD}, and the @cmd|{@{u}}| to the
|
|
remote branch name we get @cmd{remotes/origin/master..master}, and this is
|
|
short for @cmd{^remotes/origin/master master} — the set of commits made of
|
|
our master branch and all parents, minus the set of commits from the remote
|
|
branch and up — since it's ahead of that, we get an empty set. The second
|
|
log command reverses the two, giving us the set of commits that the remote
|
|
has and the local branch doesn't. In addition to @cmd{git log}, you can use
|
|
@cmd{gitk} to inspect the repository: use the @cmd{--all} flag to make it
|
|
show all branches. Either way you'll be able to see that a fast-forward
|
|
merge is possible.}
|
|
|
|
@subsection{Concurrent development}
|
|
@p*{
|
|
Again, we'll assume starting with the @path{foo2} repository before the pull.
|
|
We will now create a new commit before we get changes. This makes it similar
|
|
to commits pushed to the server while you do your work — so let's see how
|
|
this common story goes and try to push this change:
|
|
@pre{
|
|
$ echo "blah blah" > blah
|
|
$ git add blah
|
|
$ git ci -m "blah"
|
|
$ git push
|
|
To pltgit:eli/foo
|
|
! [rejected] master -> master (non-fast-forward)
|
|
error: failed to push some refs to 'pltgit:eli/foo'
|
|
To prevent you from losing history, non-fast-forward updates were rejected
|
|
Merge the remote changes before pushing again. See the 'Note about
|
|
fast-forwards' section of 'git push --help' for details.}
|
|
@~
|
|
As expected, git refuses to push our change. The terminology in the error
|
|
message is a little confusing — what it basically says is that the commit(s)
|
|
that we are trying to push are not an extension of the tip of the master
|
|
branch on the server. A “non-fast-forward update” in this case would mean
|
|
that we'd set the master branch on the remote to be the same as our branch —
|
|
but this means that whatever commit that were pushed to the server (the two
|
|
commits we pushed out from the @path{foo} clone in this case) will be lost.
|
|
@~
|
|
To merge in the remote changes, we need to pull them in. We'll now look at
|
|
three different ways to do this.}
|
|
@h3{1. Playing it safe}
|
|
@p*{
|
|
First, as we've seen above, doing a separate @cmd{git fetch} step would allow
|
|
you to see where things stand before you do anything. Alternatively, we can
|
|
use the @cmd{--ff-only} variant of @cmd{git pull}, which will do a merge only
|
|
if it's a fast-forward one, covering the trivial cases. If a fast-forward
|
|
merge cannot be done, it will tell you about it and then stop:
|
|
@pre{$ git pull --ff-only
|
|
@i{...}
|
|
From pltgit:eli/foo
|
|
87f1f02..18bc0e6 master -> origin/master
|
|
fatal: Not possible to fast-forward, aborting.}
|
|
@~
|
|
We can now use the usual tools to see where things stand. The following are
|
|
all useful here:
|
|
@ul*{
|
|
@~ @cmd{gitk --all}@br
|
|
This visualizes the commit graph. If you do this, you will see the four
|
|
commits that we have so far: the initial commit at the root, the commit
|
|
that we did in this clone, and the two commit that we retrieved and are
|
|
waiting on @cmd{remotes/origin/master}.
|
|
@~ (a) @cmd|{git log @{u}..}|@br
|
|
(b) @cmd|{git log ..@{u}}|@br
|
|
(c) @cmd|{git diff @{u}..}|@br
|
|
(c) @cmd|{git diff ..@{u}}|@br
|
|
Inspect the commit that the local branch has over the remote (a), and the
|
|
two that the remote has over the local one (b); look at the difference
|
|
between the local branch and the remote either way (c).
|
|
@~ (a) @cmd|{git log --left-right --oneline @{u}...}|@br
|
|
(b) @cmd|{git log --left-right --oneline ...@{u}}|@br
|
|
(c) @cmd|{git log --graph --oneline --all}|@br
|
|
An alternative notation for specifying commit sets for @cmd{git log} is
|
|
@cmd{R1...R2} (with @em{three} dots) — this stands for all commits from
|
|
both R1 and R2 and their parents, but excluding commits from their “merge
|
|
base” — the parent commit that both descend from. As demonstrated in (a),
|
|
this is especially useful with the @cmd{--left-right} flag: you'll see the
|
|
commits that are new on the remote branch and the ones that are new on the
|
|
local one, with @cmd{<} or @cmd{>} indicating which side each commit is
|
|
coming from. Yet another way that @cmd{git log} can be used is (c) with a
|
|
@cmd{--graph} flag, which makes it render the commit graph in ASCII-art.
|
|
@~ @cmd{git show-branch -a}@br
|
|
This is another potentially useful commands that shows how commits are
|
|
distributed over branches in your repository. In this case you will see a
|
|
matrix with the four commits in separate rows, and each will have a
|
|
@cmd{+} or @cmd{*} indicating whether it is included in a branch.
|
|
@~ (a) @cmd|{git diff @{u}...}|@br
|
|
(b) @cmd|{git diff ...@{u}}|@br
|
|
The three-dots notation is also used by @cmd{git diff}, with a slightly
|
|
different semantics than in @cmd{git log} (remember that @cmd{git log}
|
|
talks about commit sets, and @cmd{git diff} compares two specific points).
|
|
In the @cmd{diff} case, these compare a specific branch tip to the merge
|
|
base of this branch and another, which means that you see a diff with the
|
|
work done on one branch that is not included in the other (this is unlike
|
|
the two-dot syntax where you get the diff between the two branch tips).
|
|
In the first (a) example you will see all changes that you did locally,
|
|
and in (b) the changes that were done remotely.}}
|
|
@h3{2. Merging (not the fast-forward variant)}
|
|
@p*{
|
|
Now that we did a @cmd{git fetch} or a @cmd{git pull --ff-only} to update the
|
|
remote branch, we can proceed with merging it into our branch — which we can
|
|
do in one of two ways:
|
|
@pre{$ git merge origin # merge origin/master into master
|
|
Merge made by recursive.
|
|
@i{...}
|
|
# -or-
|
|
$ git pull # will do a merge as usual}
|
|
@~
|
|
Using @cmd{merge} is better for the usual reason: @cmd{git pull} can bring in
|
|
more updates that were pushed by others since you fetched, and include them
|
|
in the merge. Note that if instead of running a separate fetch or pulling
|
|
with @cmd{--ff-only} you were using @cmd{git pull}, then you'd essentially
|
|
get to the same point we are now at.
|
|
@~
|
|
Either way, the merge tells us “Merge made by recursive” — which is important
|
|
here: if we see “Fast-forward” it means that no new commits were made, but
|
|
“Merge made ...” means that a merge commit was created (“by recursive” refers
|
|
to the merge strategy, git has several of them). If we look at the commit
|
|
graph now with gitk or with @cmd{git log}, we'll see the new commit that was
|
|
created:
|
|
@pre{$ git log --oneline --graph --date-order
|
|
* 12bf7ee Merge remote branch 'origin'
|
|
|\
|
|
* | a40a45f blah
|
|
| * 18bc0e6 even more stuff
|
|
| * b7d3c41 more stuff
|
|
|/
|
|
* 87f1f02 initial content}
|
|
The merge was successful without manual intervention (you didn't need to
|
|
resolve any conflicts), so it proceeded immediately to create the commit
|
|
that connects the two lines of development, and used some standard
|
|
template for the commit log. This is the safe thing to do as the
|
|
default for git, but in this case it makes the history complicated with
|
|
no good reason — it just happened that while we were working on @cmd{blah}
|
|
someone else pushed an unrelated change. (It could be related: perhaps
|
|
one of the remote commits was referring to the file that I've added, but
|
|
especially in the PLT case this would be very rare.) If you use gitk to
|
|
look at the recent history of the repository, you'll see many such
|
|
commits, and they can make it harder to figure out how the development
|
|
went on.}
|
|
@h3{3. Rebasing}
|
|
@p*{
|
|
To get a simple/readable history, the goal is to have a more linear history:
|
|
just have the two remote commits and move our commit to follow them (which
|
|
would be the history that you get with subversion under similar conditions).
|
|
But our commit object already points to its parent, so it @em{cannot} move:
|
|
in the above graph, @cmd{a40a45f} is a hash that was computed based on
|
|
@cmd{87f1f02} being its parent, and changing a parent means getting a new
|
|
hash and therefore a new commit object.
|
|
@~
|
|
This is where @cmd{git rebase} gets into the picture. Assuming that we
|
|
didn't merge as described above, we would just use @cmd{rebase} instead:
|
|
@pre{$ git log --graph --all --oneline
|
|
* a40a45f blah # \
|
|
| * 18bc0e6 even more stuff # \
|
|
| * b7d3c41 more stuff # > same tree as before the merge
|
|
|/ # /
|
|
* 87f1f02 initial content # /
|
|
$ git rebase origin
|
|
First, rewinding head to replay your work on top of it...
|
|
Applying: blah # git tells us that it re-applies this
|
|
$ git log --graph --all --oneline
|
|
* 6ebd1fb blah # \
|
|
* 18bc0e6 even more stuff # \ the resulting history
|
|
* b7d3c41 more stuff # / is linear
|
|
* 87f1f02 initial content # /}
|
|
@~
|
|
Here's what git did in this rebase: it (1) moved the HEAD to the merge base
|
|
between your local branch and the remote one — the @cmd{87f1f02} commit; (2)
|
|
did a fast-forward merge, which just moves your local branch to the tip of
|
|
the remote one; (3) it now @em{replays the same changes} that you had in your
|
|
commits (only @cmd{a40a45f} in this case) on top of the new tip, leading to
|
|
@em{new} commit objects (@cmd{6ebd1fb} in here). So we end up with a fresh
|
|
commit object, and the old one (@cmd{a40a45f}) is gone. (It's not really
|
|
gone — it's kept in your repository store for a while, to protect you from
|
|
losing work.) If you look at the complete details of the new commit using
|
|
gitk or @cmd{git log}, you will see that this commit has different dates for
|
|
the author and the committer date:
|
|
@pre{$ git log --format=fuller -1 6ebd1fb # all details, show only one commit
|
|
@i{...}
|
|
AuthorDate: 2010-05-02 10:26:00 -0500
|
|
@i{...}
|
|
CommitDate: 2010-05-02 10:30:00 -0500}
|
|
This is because rebasing just created a new commit — but the author time is
|
|
still considered the same. In any case, you now have linear history that is
|
|
a proper descendant of the remote branch, and you can now push your changes
|
|
out.
|
|
@~
|
|
Practically every place where you read about rebasing in git will warn you
|
|
about not doing it for public history. The problem is that if someone had a
|
|
copy of your previous branch (@cmd{a40a45f}), then next time they will
|
|
update, if their copy of the branch is updated, then things will change in a
|
|
nasty way: even more so if they've committed more changes on that branch.
|
|
(This “someone” can also be yourself, of course.) This also explains why
|
|
@cmd{git pull} does not rebase by default.
|
|
@~
|
|
As far as the PLT repository goes, server will never allow pushes that are
|
|
not strict extensions of what's on it (in other words, it only allows
|
|
fast-forward pushes) — so you won't be able to mess things up for others.
|
|
But as long as your change is something that you work on privately, there is
|
|
absolutely no problem in doing this. Note that this is the same thing as
|
|
with re-doing commits because of mistakes: as long as a commit did not go
|
|
out, you can fix it in multiple ways; when it does get pushed to the server,
|
|
the only practical way to fix it is by pushing another commit. (In some
|
|
extreme cases we may do such a thing: for example, if you commit a passwords
|
|
file, then there is no other way to remove it completely from the repository
|
|
— but these are very rare, and such fixes affect everyone.)
|
|
@~
|
|
It is therefore best to get one of two habits when you do a @cmd{git pull}:
|
|
either use @cmd{--ff-only} or @cmd{--rebase}. The latter is a little more
|
|
convenient but you might not feel comfortable about doing a rebase
|
|
automatically — it @em{might} just be that someone worked on the same set of
|
|
files, and you really prefer a plain merge. For this, you might prefer using
|
|
@cmd{--ff-only} which will automatically work in the trivial cases, and
|
|
otherwise leave you in a state where you can look at things and decide how to
|
|
proceed yourself.}
|
|
|
|
@subsection{Additional forms of history tweaking}
|
|
@p*{
|
|
As described in the previous section, rebasing is not some kind of a magical
|
|
operation: it is really just an expected by-product of the way git works — of
|
|
the fact that commits can be created as descendants of any commit (not just
|
|
tip commits). You could perform a rebase manually by starting from some
|
|
commit, then inspect each of the changesets that the rebased history
|
|
contains, and play them back on the new commit. (Lumping this tediousness
|
|
lead to a script, which lead to the rebase command.) This means that you
|
|
don't really have to limit yourself to replaying these commits exactly as
|
|
they were — for example, you could write new commit log messages, combine two
|
|
commits into one, drop some commits, or reorder their order.
|
|
@~
|
|
The rebase command has a flag that makes doing all of these things easy:
|
|
@cmd{--interactive}. Continuing the above example, we now have four commits
|
|
in our history — and say that we want to tweak the last two. If we now run
|
|
@pre{$ git rebase HEAD~2}
|
|
we ask git to rebase our current head off of its grandparent commit (remember
|
|
that @cmd{HEAD^} is the parent, @cmd{HEAD^^} is the grandparent, and
|
|
@cmd{HEAD~2} is an alternative syntax for @cmd{HEAD^^}). Since it @em{is}
|
|
already based there, @cmd{git rebase} does nothing, and tells us that the
|
|
branch is up to date. But if we add @cmd{--interactive}, we get something
|
|
different: git pops up an editor with this text:
|
|
@pre{pick 18bc0e6 even more stuff
|
|
pick 6ebd1fb blah
|
|
# Rebase b7d3c41..6ebd1fb onto b7d3c41
|
|
# @i{...}}
|
|
This is a listing of the last two commits with their one-line log messages.
|
|
As the text that is below these lines say, you can replace the @cmd{pick}
|
|
before a commit with a different command: you can use @cmd{reword} to get to
|
|
write a different log message (you will get another editor window to do the
|
|
editing), @cmd{squash} to combine a commit with the previous one (it will let
|
|
you edit the log message for the combination), @cmd{fixup} which does the
|
|
same but discards the log message, and finally @cmd{edit} will make the
|
|
rebasing process stop at the relevant commit and let you tweak it before it
|
|
continues. In addition, removing a commit line means that the commit will be
|
|
skipped, and reordering lines will replay the commits in a different order.
|
|
If any of these lead to conflicts, the rebasing will stop for a manual
|
|
resolution, and you'll need to @cmd{git rebase --continue} when resolved, or
|
|
@cmd{git rebase --abort} to get back to the original state.
|
|
@~
|
|
Note that since our @path{foo2} clone tracks a public @path{foo} repository,
|
|
this particular rebase is bad: we intend to edit the last two commits, but
|
|
only one is local to @path{foo2} — the other is a commit that we got from
|
|
@path{foo}, and changing it means that we will get a rebased commit based on
|
|
its parent, and the server will forbid pushing it later. If you see that you
|
|
went too far when you see the rebase editor, all you need to do is keep those
|
|
lines untouched: in the trivial case of leaving an initial set of commits in
|
|
unmodified, they will be “rebased” by leaving them in as is. (If you inspect
|
|
the history later, you will see that they have the same SHA1s.)
|
|
@~
|
|
A useful case of using @cmd{squash} (or @cmd{fixup}) with interactive
|
|
rebasing is doing @cmd{checkpoint} commits frequently, and eventually
|
|
combining them to a single commit. This demonstrates one a popular git
|
|
principle: keep commits as logical units that correspond with the changes
|
|
done, since there is no central server that dictates a
|
|
public-commit-or-nothing. Doing these will make it easier in the future to
|
|
deal with the history: inspect the changeset as a whole, undo it, and it also
|
|
works well with finding bugs in the history — you can have checkpoints for
|
|
intermediate states of the code even if it doesn't work, since this state
|
|
will eventually be hidden.
|
|
@~
|
|
A particularly common case for editing history is “oops commits”: you just
|
|
made some change, committed it, and then realized that something is wrong —
|
|
you forgot to change some related reference, to remove some debugging
|
|
printout, or to describe some new aspect of the commit. You could use
|
|
@cmd{git rebase HEAD^} in these cases to rebase just the last commit while
|
|
editing it, but there is a much more convenient way to do this: @cmd{git
|
|
commit --amend}. Usually, @cmd{git commit} creates a new commit based on the
|
|
current branch tip and a given commit log message, but with @cmd{--amend} it
|
|
does something different: it takes a snapshot of the tree as usual, but it
|
|
makes the commit be a descendant of the tip's parent commit. For example,
|
|
assuming we didn't really change anything with the rebase above, our history
|
|
and recent change is now:
|
|
@pre|{$ git log --oneline
|
|
6ebd1fb blah
|
|
18bc0e6 even more stuff
|
|
b7d3c41 more stuff
|
|
87f1f02 initial content
|
|
$ git log --oneline -p -1 # -p => show patch, -1 => only one
|
|
6ebd1fb blah
|
|
diff --git a/blah b/blah
|
|
@i{...}
|
|
--- /dev/null
|
|
+++ b/blah
|
|
@@ -0,0 +1 @@
|
|
+blah blah # this is the recent change}|
|
|
This is obviously wrong — we need to have three “blah”s there. With
|
|
subversion we would now need to perform an “oops, forgot a blah” commit, and
|
|
in fact, we would need to do the same with git if we push these change out
|
|
now. But as long as we didn't, we can fix it without an additional commit
|
|
using @cmd{--amend}:
|
|
@pre{$ echo "blah blah blah" > blah
|
|
$ git add blah
|
|
$ git ci --amend -m "blah^3"
|
|
[master 5cf863d] blah^3 # the re-made commit
|
|
$ git log --oneline
|
|
5cf863d blah^3
|
|
18bc0e6 even more stuff
|
|
b7d3c41 more stuff
|
|
87f1f02 initial content}
|
|
As you can see, the last commit is gone (remember that it is still backed up,
|
|
in case of problems), and there is a completely new commit instead. Usually,
|
|
@cmd{--amend} is used without @cmd{-m} — the log message editor will be
|
|
initiated with the previous log message, so you can edit it instead of
|
|
rewriting it from scratch. If there were no modifications to be committed, a
|
|
@cmd{git commit --amend} is a convenient way to edit the last commit message
|
|
only. If you leave the message untouched, a new commit will still be made —
|
|
one with a new commit time; and if you delete the text completely, the
|
|
re-commit will be aborted, and you will be left with the old one intact.}
|
|
|
|
@subsection{Resetting the tree}
|
|
@p*{
|
|
Both the @cmd{commit --amend} feature and rebasing build on the ability to
|
|
“move” the current branch tip to some earlier commit in its history. To do
|
|
this directly, git provides a @cmd{git reset} command, which can move the
|
|
current branch tip to a specified commit, and adjust the working directory
|
|
and/or the staging area accordingly. For example, for the @cmd{--amend}
|
|
functionality, you will use @cmd{HEAD^} to move the branch tip to its parent
|
|
commit. You can of course specify any other commit to move to, and since git
|
|
branches are effectively short bookmarks, you can create branches to be able
|
|
to move to them later on (or as targets for rebasing, merging, etc). In
|
|
addition, you can use @cmd{HEAD} (or just omit the target, since @cmd{HEAD}
|
|
is the default) to only change the working directory (or staging area).
|
|
@~
|
|
The reset command has three major modes for its work, specified with a flag.
|
|
(See the @man{git-reset} man page for a more thorough explanation, with lots
|
|
of usage examples, some have evolved into their own functionality — like the
|
|
@cmd{--amend} feature.) Using @cmd{HEAD^} as the target commit, here are
|
|
some summaries of how it can be used:
|
|
@ul*{
|
|
@~ @cmd{git reset --hard HEAD^}@br
|
|
This will move the branch tip to the previous commit, and will change the
|
|
working tree and the staging area to match. Translation: completely
|
|
forget the last commit and any work in the working directory.
|
|
@~ @cmd{git reset --soft HEAD^}@br
|
|
Moves the branch tip, but does not change the working directory or the
|
|
staging area. Translation: undo the last commit, and leave your working
|
|
directory in a state where @cmd{git commit} will get the same change in.
|
|
(Note: not the @cmd{git ci} script — this will add changes in the working
|
|
directory, if any.)
|
|
@~ @cmd{git reset --mixed HEAD^}@br
|
|
Moves the branch tip and the staging area to the parent commit, and any
|
|
modifications done by the commit that are going to be lost are put in your
|
|
working directory. Translation: similar to the @cmd{--soft} version,
|
|
except that the staging area is cleared, so to recommit the changes you
|
|
will need to add files again (or use @cmd{git ci} as usual).
|
|
@br
|
|
(Note: This is the default mode.)}
|
|
@~
|
|
When using @cmd{HEAD} (which is the also default when nothing is mentioned),
|
|
the branch tip is not moved, and we get:
|
|
@ul*{
|
|
@~ @cmd{git reset --hard}@br
|
|
Get rid of all changes to the working directory and the staging area.
|
|
Translation: lose all work that was not committed, getting back to the
|
|
content on the branch (a convenient way to do something similar to an
|
|
@cmd{svn revert -R .} in the root of a subversion working directory).
|
|
@~ @cmd{git reset --mixed}@br
|
|
Get rid of all changes to the staging area, leaving your working directory
|
|
intact. Translation: lose everything that was added to the staging area.
|
|
If you're avoiding it (for example, if you only use the @cmd{git ci}
|
|
script), then this would be a no-op other than new files that were
|
|
@cmd{git add}ed.
|
|
@~ (@cmd{git reset --soft} is a no-op.)}
|
|
@~
|
|
When @cmd{git reset} changes the HEAD, it creates another toplevel reference
|
|
name called @cmd{ORIG_HEAD} that points to the previous commit that
|
|
@cmd{HEAD} pointed at, so if you happen to @cmd{git reset --hard HEAD^} by
|
|
mistake, you can immediately get back to it with
|
|
@cmd{git reset --hard ORIG_HEAD} (but changes in the working tree would still
|
|
be lost). (@cmd{git merge} is another command that changes the @cmd{HEAD},
|
|
and it also saves the previous value in @cmd{ORIG_HEAD}.) Finally, note that
|
|
@cmd{git reset} can be restricted to make it work only on a specific set of
|
|
paths, not on the whole repository.}
|
|
|
|
@subsection{Other forms of reverting}
|
|
@p*{
|
|
While we're on the topic of reverting files, there are three more things
|
|
worth mentioning:
|
|
@ul*{
|
|
@~ @cmd{git checkout -- @i{path ...}}@br
|
|
@p*{
|
|
When @cmd{git checkout} is given some paths, it will only check out the
|
|
relevant files from their state in the staging area. This is a more
|
|
popular way to revert changes to a specific file. If you avoid using
|
|
the staging area, then this is roughly the same as using reset with the
|
|
@cmd{--hard} flag on the paths, since your staging area will usually be
|
|
the same as your @cmd{HEAD}. (Note that the @cmd{--} is optional, and
|
|
needed only when a path name can be confused with a branch name.)
|
|
@~
|
|
As with @cmd{reset}, you can also specify a branch to check the path(s)
|
|
from — which is useful to try some files from a different branch
|
|
selectively. However, note that unlike subversion, git does not
|
|
remember the association of the branch and the paths that were checked
|
|
out of it (the branch is not “sticky”) — the files will simply be
|
|
considered as modified (and they will not be updated when the branch is,
|
|
unless you do the same checkout).}
|
|
@~ @cmd{git show HEAD:@i{path}}@br
|
|
@p*{
|
|
This shows the file as it exists in the @cmd{HEAD}, making it useful to
|
|
inspect the file before you made some additional modifications (similar
|
|
to @cmd{svn cat @i{path}}). You can also omit the @cmd{HEAD} — using
|
|
@cmd{:@i{path}} will show the file in the staging area, which will
|
|
usually be the same as the @cmd{HEAD}. One caveat to note here is that
|
|
the path should be the full path relative to the repository root.
|
|
(Note: I have a wrapper @cmd{git cat} script that emulates
|
|
@cmd{svn cat}, I'll add it if anyone wants.)}
|
|
@~ @cmd{git revert @i{commit}}@br
|
|
@p*{
|
|
The @cmd{git revert} command is used to revert the changes introduced by
|
|
the given commit. It will basically apply the change in that commit
|
|
in reverse, then ask you for a log message for a new commit where the
|
|
message is initially populated with text indicating the commit that
|
|
was applied in reverse.
|
|
@~
|
|
Note that this is very different from @cmd{svn revert} — it is more like
|
|
@pre{svn merge -c-123@";" svn commit "Revert revision 123"}
|
|
Since this is a frequent source of confusion, the @man{git-revert} man
|
|
page mentions it at the top, and it refers readers to @cmd{git reset}
|
|
and @cmd{git checkout} as the way to do the equivalent of @cmd{svn
|
|
revert} (which are described above.)}}}
|
|
|
|
@subsection{Dealing with conflicts}
|
|
@p*{
|
|
We'll now see how to deal with merge conflicts. First, we'll set up the
|
|
repository for a conflict. Continuing with the @path{foo2} clone, we'll
|
|
first create a file (which I'll do here using shell commands, to make it easy
|
|
to play with), commit, and push the new history (which includes the blah
|
|
work) back to the server. Note the use of @cmd{git branch -v} which shows
|
|
the local @cmd{master} branch and the fact that there's two commits that we
|
|
haven't pushed out yet.
|
|
@pre{$ echo "#lang racket" > foo
|
|
$ echo "(define (foo x)" >> foo
|
|
$ echo " (* x x))" >> foo
|
|
$ git ci -m "turn foo into a library"
|
|
[master fd856ef] turn foo into a library
|
|
$ git branch -v
|
|
* master fd856ef [ahead 2] turn foo into a library
|
|
$ git push
|
|
To pltgit:eli/foo
|
|
18bc0e6..fd856ef master -> master}
|
|
@~
|
|
Now hop over to the @path{foo} clone, get the changes (the relevant bits of
|
|
the output are shown), edit the file (using sed, to make it a command line),
|
|
inspect the change, commit it, and push.
|
|
@pre|{$ cd ../foo
|
|
$ git pull
|
|
From pltgit:eli/foo
|
|
18bc0e6..fd856ef master -> origin/master
|
|
Updating 18bc0e6..fd856ef
|
|
Fast-forward
|
|
blah | 1 +
|
|
foo | 6 +++---
|
|
2 files changed, 4 insertions(+), 3 deletions(-)
|
|
create mode 100644 blah
|
|
$ sed -i '2s/x/[x 0]/' foo
|
|
$ git diff
|
|
diff --git a/foo b/foo
|
|
index 78d9889..b81de80 100644
|
|
--- a/foo
|
|
+++ b/foo
|
|
@@ -1,3 +1,3 @@
|
|
#lang racket
|
|
-(define (foo x)
|
|
+(define (foo [x 0])
|
|
(* x x))
|
|
$ git ci -m 'add a default value'
|
|
[master 5035c9a] add a default value
|
|
$ git push
|
|
To pltgit:eli/foo
|
|
fd856ef..5035c9a master -> master}|
|
|
@~
|
|
And now get back to @path{foo2}, and before we pull, modify the same line by
|
|
adding a comment and commit, then do a @cmd{--ff-only} pull and watch it
|
|
refuse to merge as expected, then look at the history so far.
|
|
@pre{$ cd ../foo2
|
|
$ sed -i '2s/$/ ; int->int/' foo
|
|
$ git ci -m 'document the type of foo'
|
|
[master 21a78df] document the type of foo
|
|
$ git pull --ff-only
|
|
From pltgit:eli/foo
|
|
fd856ef..5035c9a master -> origin/master
|
|
fatal: Not possible to fast-forward, aborting.
|
|
$ git log --graph --all --oneline -4
|
|
* 21a78df document the type of foo # ← our change
|
|
| * 5035c9a add a default value # ← the conflicting change we pulled
|
|
|/
|
|
* fd856ef turn foo into a library
|
|
* 5cf863d blah^3}
|
|
@~
|
|
Rebasing is the common thing to do, but let's see what happens with a plain
|
|
@cmd{merge} first:
|
|
@pre{$ git merge origin
|
|
Auto-merging foo
|
|
CONFLICT (content): Merge conflict in foo
|
|
Automatic merge failed; fix conflicts and then commit the result.}
|
|
@~
|
|
We now have a conflict that needs to be resolved before we can finish the
|
|
merge. Using @cmd{git st} (the alias listed above for the svn-like status
|
|
that @cmd{git status -s} produces) shows a new @cmd{UU} status for @path{foo}
|
|
— this indicates an “unmerged” (conflicted) file. To investigate further, we
|
|
use a plain @cmd{git status}, which tells us that our history diverged from
|
|
the remote (we already know that since @cmd{pull --ff-only} failed) and count
|
|
the diverging commits, and it also tells us that @path{foo} is unmerged and
|
|
hints at using @cmd{git add} to resolve it:
|
|
@pre{$ git st
|
|
UU foo
|
|
$ git status
|
|
# On branch master
|
|
# Your branch and 'origin/master' have diverged,
|
|
# and have 1 and 1 different commit(s) each, respectively.
|
|
# Unmerged paths:
|
|
# (use "git add/rm <file>..." as appropriate to mark resolution)
|
|
# both modified: foo}
|
|
You can also see that git knows about the conflict and refuses to do a
|
|
commit:
|
|
@pre{
|
|
$ git commit
|
|
fatal: 'commit' is not possible because you have unmerged files.
|
|
Please, fix them up in the work tree, and then use 'git add/rm <file>' as
|
|
appropriate to mark resolution and make a commit, or use 'git commit -a'.}
|
|
@~
|
|
In most cases the way to continue is simple: open the conflicted file in your
|
|
editor, look for the conflict markers and fix the code. Then, as suggested
|
|
above, use @cmd{git add @i{file}} which tells git that the file is resolved,
|
|
and finally use @cmd{git commit} to commit the result. (Note that using
|
|
@cmd{git commit @i{file}} will not work, which is why the @cmd{git-ci} script
|
|
avoids adding a @path{.} if the tree requires resolving a merge.) I'll
|
|
simulate the editing part with echos, and then mark it resolved:
|
|
@pre{$ echo "#lang racket" > foo
|
|
$ echo "(define (foo [x 0]) ; int->int" >> foo
|
|
$ echo " (* x x))" >> foo
|
|
$ git add foo # ← tell git that it's resolved}
|
|
And now the last step is to run @cmd{git commit}, which will start your
|
|
editor to edit the log message — it will be populated by text that indicates
|
|
the merge and the file that had conflicts, which you can commit as is, or add
|
|
some text regarding the way it was resolved.
|
|
@~
|
|
At this point (or before we started working on resolving the conflict), we
|
|
can get back to the original state using @cmd{reset}:
|
|
@pre{$ git reset --hard
|
|
HEAD is now at 21a78df document the type of foo}
|
|
@~
|
|
This kind of reset is generally useful if you had some problematic conflict
|
|
to resolve and you want to back up completely and re-try. But now that we've
|
|
at the start, we will see what happens when we try to rebase with the
|
|
conflict instead:
|
|
@pre{
|
|
$ git rebase origin
|
|
First, rewinding head to replay your work on top of it...
|
|
Applying: document the type of foo
|
|
@i{...}
|
|
CONFLICT (content): Merge conflict in foo
|
|
Failed to merge in the changes.
|
|
@i{...}
|
|
When you have resolved this problem run "git rebase --continue".
|
|
If you would prefer to skip this patch, instead run "git rebase --skip".
|
|
To restore the original branch and stop rebasing run "git rebase --abort".}
|
|
@~
|
|
Obviously, we get a different message (note that @cmd{git status} will now
|
|
tell you that you're not currently on any branch — a result of being in the
|
|
middle of a rebase). The process that follows is very similar to the merge
|
|
case: edit the conflict away, then @cmd{git add} the file. There are two
|
|
differences: (1) after you @cmd{git add} the resolved files, you should use
|
|
@cmd{git rebase --continue} instead of committing[*]; (2) if you want to
|
|
abort the merge, use @cmd{git rebase --abort} instead of using reset.
|
|
@small{([*] If you did commit, then it means that you wrote a new log message
|
|
for the replayed commit, and you can just as well use the @cmd{--skip} flag
|
|
so rebasing continues with the rest, or you can use @cmd{reset} to undo
|
|
your commit and let rebase do it for you.)}
|
|
@~
|
|
When you're in a conflicted state, there are a few git tools that help you in
|
|
the resolution work. The first useful utility is @cmd{git diff}: when
|
|
there's a conflict, all files that were automatically merged are already
|
|
going to be in your staging area, and parts of conflicted files that could be
|
|
merged merged will be there too. This leaves only the conflict regions in
|
|
your working directory, which means that @cmd{git diff} will show you only
|
|
the conflicts (since by default it shows differences between the working
|
|
directory and the staging area). Also, the diff output itself is not a
|
|
standard one. At the current point of conflict during the rebase that we
|
|
started, this is what we'll see:
|
|
@pre|{$ git diff
|
|
diff --cc foo
|
|
index b81de80,86a4c54..0000000
|
|
--- a/foo
|
|
+++ b/foo
|
|
@@@ -1,3 -1,3 +1,7 @@@
|
|
#lang racket
|
|
++<<<<<<< HEAD
|
|
+(define (foo [x 0])
|
|
++=======
|
|
+ (define (foo x) ; int->int
|
|
++>>>>>>> document the type of foo
|
|
(* x x))}|
|
|
@~
|
|
The diff header uses @cmd{--cc} which indicates git's “combined diff format”,
|
|
used to represent merge commits (any commit with more than one parent). The
|
|
next line has the two SHA1s of the two files that are merged. The diff
|
|
itself starts with three @cmd|{@}|s, and instead of a single indicator
|
|
character (@cmd{+}, @cmd{-}, or @cmd{ }), there are two — indicating a
|
|
three-way diff between the two versions and their common ancestor version.
|
|
In the above you can see that the line with the optional argument is coming
|
|
from @cmd{HEAD}, and the type-annotated one is coming from its commit. You
|
|
might notice that this look backwards, since we're in the repository where we
|
|
committed the type annotation to the HEAD — but we're now rebasing, which
|
|
means that we start from the remote branch and merge our local changes into
|
|
it, essentially making the rebase perform merges in the other way than plain
|
|
merges. The conflict markers themselves are marked as new in both versions,
|
|
and the labels that follow them depend on available information (in a
|
|
@cmd{merge}, we would see @cmd{HEAD} and @cmd{origin}).
|
|
@~
|
|
During a conflict resolution, the staging area actually holds three versions
|
|
of each file: the common ancestor, our version, and the merged version.
|
|
These things are called “file stages”, and they can be accessed using a
|
|
special syntax:
|
|
@pre{$ git show :1:foo # the common ancestor of both versions
|
|
$ git show :2:foo # our version (optional argument)
|
|
$ git show :3:foo # merged version (type-annotated)}
|
|
(Again, remember that this is a rebase, so the last two are swapped.) You
|
|
can also checkout one of these versions using @cmd{git checkout foo}, giving
|
|
it an @cmd{--ours} or @cmd{--theirs} flag to specify which version you want
|
|
to use; and you can use @cmd{git diff} to compare against them. For example,
|
|
we resolve the file (as above) and then try the different diffs (before we
|
|
mark it as resolved) — these examples only show the changed lines from each
|
|
of the diffs:
|
|
@pre{$ echo "#lang racket" > foo
|
|
$ echo "(define (foo [x 0]) ; int->int" >> foo
|
|
$ echo " (* x x))" >> foo
|
|
$ git diff -1 foo # can also use --base
|
|
-(define (foo x) # original version
|
|
+(define (foo [x 0]) ; int->int # new version
|
|
$ git diff -2 foo # can also use --ours
|
|
-(define (foo [x 0])
|
|
+(define (foo [x 0]) ; int->int
|
|
$ git diff -3 foo # can also use --theirs
|
|
-(define (foo x) ; int->int
|
|
+(define (foo [x 0]) ; int->int}
|
|
@~
|
|
Finally, @cmd{git log} and @cmd{gitk} accept a @cmd{--merge} flag which shows
|
|
commits relevant to a merge. With @cmd{git log} the @cmd{--left-right} flag
|
|
is useful here, since you'll see which side the relevant commits are on.
|
|
(But this works only in @cmd{git merge}, not in rebasing.)
|
|
@~
|
|
Again, when you're happy with the resolution, you @cmd{git add} the file, and
|
|
because we're doing a @cmd{rebase} rather than a @cmd{merge}, use use it to
|
|
continue:
|
|
@pre{$ git add foo
|
|
$ git rebase --continue
|
|
Applying: document the type of foo
|
|
$ git log --graph --all --oneline -4
|
|
@i{...linear history...}}
|
|
Note that @cmd{git rebase --continue} did the commit of the resolved content
|
|
for you, and it used the previous commit message you've written. This is a
|
|
good rule-of-thumb for deciding whether you should rebase or merge: if the
|
|
commit message are still fine as a description of the modifications, then a
|
|
rebase is fine; otherwise you might want to @cmd{merge} instead.}
|
|
|
|
@subsection{Copying/renaming files}
|
|
@p*{
|
|
Git is, by design, tracking snapshots of the complete repository tree.
|
|
Specifically, it does @em{not} keep explicit track of file/directory copies
|
|
and renames. Instead, it provides ways to infer such changes in the
|
|
repository based on the content. As a result of this, there are almost no
|
|
git commands that deal with file movements:
|
|
@ul*{
|
|
@~ There is no @cmd{git copy} command: you just copy the file and add the new
|
|
one as usual.
|
|
@~ There @em{is} a @cmd{git rm} command, but its purpose is mostly to remove
|
|
a file from the staging area. You could also just remove the file outside
|
|
of git, and then use either @cmd{git commit @i{removed-file}} or
|
|
@cmd{git commit @i{containing-directory}} to remove it (or using the above
|
|
script — @cmd{git ci} in the same directory). @cmd{git rm} will delete
|
|
the file from the staging area so you can do a plain @cmd{git commit}
|
|
without naming any paths.
|
|
@~ For the same reason, there is a @cmd{git mv} command — it uses
|
|
@cmd{git rm} as above to update the staging area, and if you're fine with
|
|
ignoring it, then you can just rename the file outside of git, and
|
|
@cmd{git add} the new version — but as we will soon see, it's really best
|
|
to use @cmd{git mv} to avoid the possible confusion if you want the file's
|
|
history to be visible.}
|
|
@~
|
|
To try things out, let's properly name the @path{foo} library:
|
|
@pre{$ mv foo foo.rkt
|
|
$ git st
|
|
D foo
|
|
?? foo.rkt}
|
|
As you can see, we forgot to @cmd{git add} the new file, so if we commit now
|
|
we'll only be committing the deletion. An important thing to note here is
|
|
that when git infers file copying and renaming, it does so only when the
|
|
operations appear in a @em{single} commit. So if we commit this change and
|
|
later commit a new version with the new file will make it lose connection to
|
|
its history. As long as you didn't push the new commits out, you can still
|
|
fix it: simply use @cmd{git rebase --interactive}, and squash the file
|
|
addition together with the deletion. But let's start over and do the rename
|
|
the easy way:
|
|
@pre{$ rm foo.rkt
|
|
$ git reset --hard
|
|
$ git mv foo foo.rkt
|
|
$ git ci -m "properly name the foo library"}
|
|
to see this commit, we can use @cmd{git show} (which can show arbitrary
|
|
objects, but with no arguments it shows the @cmd{HEAD}). @cmd{git diff} can
|
|
also be used to show only the diff part — using the @cmd{HEAD^!} syntax that
|
|
roughly means the range from the previous HEAD to the current one:
|
|
@pre{$ git show
|
|
@i{...log message...}
|
|
@i{...addition+deletion...}
|
|
$ git diff HEAD^!
|
|
@i{...addition+deletion...}
|
|
$ git diff --stat HEAD^! # shows an overview of the changes
|
|
foo | 3 ---
|
|
foo.rkt | 3 +++
|
|
2 files changed, 3 insertions(+), 3 deletions(-)
|
|
$ git log --oneline foo.rkt
|
|
599b3b6 properly name the foo library}
|
|
All of these show the two operations as disconnected, and the log doesn't
|
|
show any of the prior history. The thing is that you need to ask git to look
|
|
for file operations, and the @cmd{-M} and @cmd{-C} flags do that. In
|
|
addition, @cmd{git log} needs a @cmd{--follow} flag to make it follow history
|
|
beyond renames (but note that it can do that only when given a single file
|
|
path). For example:
|
|
@pre{$ git diff -M --stat HEAD^!
|
|
foo => foo.rkt | 0
|
|
1 files changed, 0 insertions(+), 0 deletions(-)
|
|
$ git log --oneline --follow foo.rkt
|
|
599b3b6 properly name the foo library
|
|
0fb8291 document the type of foo
|
|
5035c9a add a default value
|
|
@i{...}}
|
|
In this case the rename was a trivial one as were no other changes. This
|
|
makes it especially easy to find renames since the SHA1 of the file would be
|
|
the same. But git considers such operations as renames as long as they're
|
|
“similar enough” — for example, if you just rename some files and change some
|
|
@cmd{require}s as a result, it will be detected as renames. (The usual claim
|
|
is that when the content is not similar enough, you can just as well claim
|
|
that the file is new.) If you think that you might be doing too many changes
|
|
to some files, and you want to preserve the connection, you can do only the
|
|
rename in one commit, and then the modifications in the next.
|
|
@~
|
|
An added benefit of this mode of work is that @cmd{git blame} can find lines
|
|
in files that were copied from other files, and deal naturally with a file
|
|
that is split into two files etc. Like @cmd{log} and @cmd{diff}, it needs
|
|
some flags to do the extra work (see @cmd{-M} and @cmd{-C}).}
|
|
|
|
@subsection{Managing branches}
|
|
@p*{
|
|
As seen in various places above, a branch in git is basically just a SHA1
|
|
pointer to a commit (and therefore to the whole line of commits in its
|
|
development line), with a naming hierarchy that follows some conventions
|
|
(@path{/}-separated, @cmd{master} as the main one, @cmd{remotes} prefix for
|
|
remote branches, @cmd{origin} for the default remote server name, etc). You
|
|
can see all of this in the toplevel @path{.git} meta directory — there is a
|
|
@path{HEAD} file which represents the head, its content will be a line that
|
|
looks like @cmd{ref: refs/heads/master}, and there will be a
|
|
@cmd{refs/heads/master} file with a content that is the actual SHA1. There
|
|
are, of course, various other bits of meta-data, so it's not a good idea to
|
|
change such files directly (for example, when there are many names git will
|
|
create a “packed” reference file with many references for efficiency) — but
|
|
overall this is the basic idea.
|
|
@~
|
|
Branches come in two main kinds: local branches and remote ones, with remote
|
|
branches having a name that begins with @cmd{remotes/origin/}. (Later we'll
|
|
see how to add new remote repositories — remote branches from these will have
|
|
names that start with @cmd{remotes/@i{remote-name}/} instead.) The
|
|
difference between the two is that a remote branch is a way to mirror a
|
|
branch on a remote repository — it is not intended for local work. For
|
|
example, if you try to check out a remote branch, git will check out a
|
|
“detached head” (details on this below). If you do that, you'll see that the
|
|
@path{HEAD} file will have an explicit SHA1 rather than the usual
|
|
@cmd{ref: @i{branch-name}}.
|
|
@~
|
|
The @cmd{git branch} command is the main way to manage branches. With no
|
|
flags, it will just print out the list of local branches, marking the current
|
|
branch with a @cmd{*}. You can add flags to show remote branches instead
|
|
(@cmd{-r}), both kinds (@cmd{-a}), and also to list more information on the
|
|
branches (@cmd{-v}):
|
|
@pre{$ git branch
|
|
* master
|
|
$ git branch -r
|
|
origin/HEAD -> origin/master # (this one is symbolic too)
|
|
origin/master
|
|
$ git branch -av
|
|
* master 599b3b6 [ahead 2] properly name the foo library
|
|
remotes/origin/HEAD -> origin/master
|
|
remotes/origin/master 5035c9a add a default value}
|
|
@~
|
|
When given a single name argument, a branch by that name will be created, and
|
|
it will point to where the @cmd{HEAD} currently points to; a second argument
|
|
can be a name of an existing branch (or any commit) that the new branch will
|
|
start at. In addition to creating branches starting from the current head,
|
|
this can be useful in creating branches that start from elsewhere, even from
|
|
a “detached head”. For example, say that in our current repository we want
|
|
to try out some work based on the state of things before the last commit
|
|
(which renamed the @path{foo} file). We can check out @cmd{HEAD^} (which
|
|
will lead to a detached HEAD), and then create a branch for it:
|
|
@pre{$ git checkout HEAD^
|
|
@i{...}
|
|
You are in 'detached HEAD' state.
|
|
@i{...}
|
|
HEAD is now at 0fb8291... document the type of foo
|
|
$ cat .git/HEAD
|
|
0fb8291... # doesn't point to a branch
|
|
$ git branch
|
|
* (no branch) # you can see it here too
|
|
master
|
|
$ git status
|
|
# Not currently on any branch. # and here
|
|
$ git branch pre-rename # create a branch here
|
|
$ git branch
|
|
* (no branch) # we're still detached
|
|
master
|
|
pre-rename
|
|
$ git checkout pre-rename
|
|
Switched to branch 'pre-rename'}
|
|
As you can see, creating a branch doesn't check it out — even when the new
|
|
branch is exactly where we already are. The difference is related to the
|
|
nature of @cmd{HEAD}: it is usually an indirect reference to a branch name,
|
|
and when a commit is made, the branch that @cmd{HEAD} points to is updated.
|
|
But when we are using a detached HEAD, it points directly at a SHA1 —
|
|
committing in this state will work, and the HEAD will point at the newly made
|
|
commit — but there will be no branch that will be updated, so if you checkout
|
|
a different branch (or a different commit) now, the commits you made are
|
|
“lost”.
|
|
@~
|
|
The main reason that such commits will be lost is that git branches don't
|
|
live inside the repository store — and dealing with branches is not something
|
|
that gets recorded as part of the history. To make things safer, git
|
|
maintains something that is known as the “reflog”, which keeps track of where
|
|
your branches have been — those are kept for a while (usually around a
|
|
month), which means that you can easily go back to a previous commit if it
|
|
seems that you lost one (eg, as a result of committing on a detached HEAD).
|
|
(You can see these files in the @path{.git/logs} directory.)
|
|
@~
|
|
Since creating a new branch and checking it out is a common combination, the
|
|
@cmd{checkout} command can create a branch before checking it out. Use the
|
|
@cmd{-b} flag for this:
|
|
@pre{$ git checkout -b also-pre-rename
|
|
Switched to a new branch 'also-pre-rename'
|
|
$ git checkout -b post-rename master
|
|
Switched to a new branch 'post-rename'
|
|
$ ls
|
|
bar blah foo.rkt
|
|
$ echo "one more line" >> foo.rkt
|
|
$ git ci -m "one more line"}
|
|
@~
|
|
Finally, you use the @cmd{-d} flag to delete branches.
|
|
@pre{
|
|
$ git branch -d post-rename # won't allow it
|
|
error: Cannot delete the branch 'post-rename' which you are currently on.
|
|
$ git checkout master
|
|
Switched to branch 'master'
|
|
Your branch is ahead of 'origin/master' by 2 commits.
|
|
$ git branch -d post-rename
|
|
error: The branch 'post-rename' is not fully merged.
|
|
If you are sure you want to delete it, run 'git branch -D post-rename'.}
|
|
As you can see, git refuses to delete a branch that has unmerged work, since
|
|
this can lead to losing that unmerged work — so you need to use @cmd{-D} for
|
|
that. In addition, you usually don't delete remote branches, when you do,
|
|
you need to use the @cmd{-r} flag too.}
|
|
|
|
@subsection{Using branches}
|
|
@p*{
|
|
Since git branches are so light weight, they fit any kind of parallel work
|
|
you need to do on several different topics. A result of that is that it is
|
|
possible to start a new branches for any work you'd want to do — and this is
|
|
common enough that there's a name for such branches, they're called “topic
|
|
branches”. Such branches are created from the master branch (usually) and
|
|
worked on in parallel. At any point where you want to work on something new,
|
|
you would create a new branch for it and switch to it (committing any work
|
|
you might have on your current branch before you do so):
|
|
@pre{$ git checkout -b improve-bar master # switch to a fresh topic branch
|
|
Switched to a new branch 'improve-bar'
|
|
$ echo "even more bar" >> bar # work there
|
|
$ git ci -m "improved bar" # save that work
|
|
$ git checkout post-rename # go back to where we were}
|
|
@~
|
|
If you need to commit changes before you create the new branch, you shouldn't
|
|
have any problems doing so — because you can change where a branch points to,
|
|
you can just commit whatever you have and then get back to it:
|
|
@pre{
|
|
$ echo "another line" >> foo.rkt
|
|
# at this point you remember that you need to do something else in the
|
|
# `improve-bar' line of work.
|
|
$ git ci -m "checkpoint"
|
|
$ git checkout improve-bar
|
|
# ...work...
|
|
$ git checkout post-rename
|
|
$ git log --oneline -2
|
|
e9a4fcd checkpoint # this is our temporary checkpoint commit
|
|
d92fb0a one more line
|
|
$ git reset HEAD^ # undo it
|
|
Unstaged changes after reset:
|
|
M foo.rkt # git tells us that this is now uncommitted
|
|
$ git st
|
|
M foo.rkt # ... as does `status'
|
|
$ git log --oneline -2
|
|
d92fb0a one more line # the temporary commit is gone
|
|
599b3b6 properly name the foo library}
|
|
@~
|
|
You can even decide on some convention to use in some cases, then create new
|
|
git commands as scripts that will do the work for you. In this case, you
|
|
could write a command that will do a “checkpoint” commit if needed, switch to
|
|
another branch, and if the first commit there has only @cmd{checkpoint} as
|
|
its log message, undo it as above. There are several git convenience
|
|
commands that started out this way — in this case, checkout the @cmd{git
|
|
stash} command which allows you to save the current work by pushing it on a
|
|
“work in progress” stack, and later pop it back out (possibly on a different
|
|
branch).
|
|
@~
|
|
Earlier we've seen how to merge or rebase your master branch from the remote
|
|
master branch, but the full story is that you can merge and rebase @em{any}
|
|
two branches. This makes branches very flexible: you can create a branch A
|
|
from an existing branch B, eventually merging/rebasing it back into A, or
|
|
directly into master and dump A. At any point you can run @cmd{gitk --all}
|
|
to see where things stand — in our current repository, this shows us that
|
|
there are redundant @cmd{pre-rename} and @cmd{also-pre-rename} branches, that
|
|
out @cmd{master} branch is two commits ahead of the remote one, and that we
|
|
have @cmd{improve-bar} and @cmd{post-rename} branches with 1 and 2 commits
|
|
over our @cmd{master} branch. If we're done with these two branches, we can
|
|
now merge/rebase them to our @cmd{master}, or merge/rebase one to the other
|
|
and the result to @cmd{master}, and then push everything out.
|
|
@~
|
|
To make working with branches even easier, git has a notion of an “upstream
|
|
branch” — this is a per-branch setting that tells git which branch the
|
|
current one is based on. By default, any branch that is created with a
|
|
remote branch as its starting point will have that remote branch set as its
|
|
upstream. We've seen how git treats that information in various places so
|
|
far: @cmd{git status} and @cmd{git branch -v} both use it, and using a second
|
|
@cmd{-v} with the latter shows also the upstream branch:
|
|
@pre{
|
|
$ git reset --hard # dump the above uncommitted change
|
|
$ git checkout master
|
|
Switched to branch 'master'
|
|
Your branch is ahead of 'origin/master' by 2 commits.
|
|
$ git status
|
|
# On branch master
|
|
# Your branch is ahead of 'origin/master' by 2 commits.
|
|
@i{...}
|
|
$ git branch -v
|
|
@i{...}
|
|
* master 599b3b6 [ahead 2] properly name the foo library
|
|
@i{...}
|
|
$ git branch -vv
|
|
* master 599b3b6 [origin/master: ahead 2] properly name the foo library}
|
|
@~
|
|
In addition to that, we've seen the @cmd|{@{upstream}}| and @cmd|{@{u}}|
|
|
notation that refers to the upstream branch, making it convenient to further
|
|
examine pending changes that weren't incorporated upstream:
|
|
@pre|{$ git log --oneline @{upstream}..
|
|
599b3b6 properly name the foo library
|
|
0fb8291 document the type of foo}|
|
|
@~
|
|
And finally, @cmd{git pull} and @cmd{git push} know where to pull from and
|
|
push to based on this setting. Overall, this is a very useful feature to
|
|
have when you have many branches, therefore it is possible to use it between
|
|
local branches too. There are two ways to do this: when a branch is created
|
|
with either @cmd{git branch B} or @cmd{git checkout -b B}, you can use the
|
|
@cmd{--track} flag to set up tracking to the initial branch it's based on.
|
|
@pre{$ git branch -t b1 master
|
|
Branch b1 set up to track local branch master.
|
|
$ git checkout -tb b2 master
|
|
Branch b2 set up to track local branch master.
|
|
Switched to a new branch 'b2'}
|
|
(Note: if you're using @cmd{checkout}, then the @cmd{--track} flag should
|
|
precede the @cmd{-b} flag, as done above.) If a branch already exists, you
|
|
can use @cmd{git branch --set-upstream} to set the upstream information.
|
|
@pre{$ git branch --set-upstream post-rename
|
|
Branch post-rename set up to track local branch b2.
|
|
$ git branch --set-upstream improve-bar master
|
|
Branch improve-bar set up to track local branch master.}
|
|
As seen here, if it is given just a branch name, the current branch is set as
|
|
its upstream. @cmd{git branch} can also change the upstream branch, for
|
|
example, if the above tracking of @cmd{b2} was a mistake:
|
|
@pre{$ git branch --set-upstream post-rename master
|
|
Branch post-rename set up to track local branch master.}
|
|
Either way, we can now see this information in the git commands that do so,
|
|
as well as use @cmd|{@{upstream}}|:
|
|
@pre|{$ git branch -vv
|
|
b1 599b3b6 [master] properly name the foo library
|
|
* b2 599b3b6 [master] properly name the foo library
|
|
improve-bar e60c168 [master: ahead 1] improved bar
|
|
master 599b3b6 [origin/master: ahead 2] properly name the foo @;
|
|
library
|
|
post-rename d92fb0a [master: ahead 1] one more line
|
|
$ git checkout improve-bar
|
|
Switched to branch 'improve-bar'
|
|
Your branch is ahead of 'master' by 1 commit.
|
|
$ git log --oneline @{upstream}..
|
|
e60c168 improved bar}|
|
|
@~
|
|
In addition, we can use @cmd{git pull} to get changes on the upstream branch
|
|
merged or rebased on the current one:
|
|
@pre{$ git pull
|
|
From .
|
|
* branch master -> FETCH_HEAD
|
|
Already up-to-date.}
|
|
Nothing actually happened here, because the current branch
|
|
(@cmd{improve-bar}) already contains all of the commits on the master branch.
|
|
You can see that this is a local pull since git says @cmd{From .}, which
|
|
stands for “our own repository”. You can also do a @cmd{push} now, which
|
|
will make the current additional commit (listed with @cmd|{@{upstream}..}|)
|
|
appear on the @cmd{master} branch:
|
|
@pre{$ git push
|
|
To .
|
|
599b3b6..e60c168 improve-bar -> master}
|
|
@~
|
|
Since the @cmd{improve-bar} line of development is unrelated to the one in
|
|
@cmd{post-rename}, it is now one commit behind the @cmd{master} branch, and
|
|
cannot be pushed as is:
|
|
@pre{
|
|
$ git checkout post-rename
|
|
Switched to branch 'post-rename'
|
|
Your branch and 'master' have diverged,
|
|
and have 1 and 1 different commit(s) each, respectively.
|
|
$ git branch -vv
|
|
improve-bar e60c168 [master] improved bar
|
|
master e60c168 [origin/master: ahead 3] improved bar
|
|
* post-rename d92fb0a [master: ahead 1, behind 1] one more line
|
|
$ git push
|
|
To .
|
|
! [rejected] post-rename -> master (non-fast-forward)
|
|
error: failed to push some refs to '.'
|
|
To prevent you from losing history, non-fast-forward updates were rejected
|
|
@i{...}}
|
|
Dealing with this is similar to dealing with updates on the remote server —
|
|
for example, we can rebase the branch before pushing it:
|
|
@pre{$ git pull --rebase
|
|
From .
|
|
* branch master -> FETCH_HEAD
|
|
First, rewinding head to replay your work on top of it...
|
|
Applying: one more line
|
|
$ git push
|
|
To .
|
|
e60c168..7bdec0c post-rename -> master}
|
|
@~
|
|
When you use @cmd{git push} to push changes when you have no upstream branch
|
|
set, or when you push to a different branch than the one set, you can use
|
|
@cmd{--set-upstream} to make git remember the push target as the upstream.
|
|
Therefore, an easy way to create a new branch that tracks a possibly new
|
|
remote branch by the same name is:
|
|
@pre{$ git checkout -b my-branch
|
|
Switched to a new branch 'my-branch'
|
|
$ git push origin my-branch --set-upstream
|
|
To pltgit:eli/foo
|
|
* [new branch] my-branch -> my-branch
|
|
Branch my-branch set up to track remote branch my-branch from origin.}
|
|
And when you deal with remote branches this way, you might want to have a
|
|
local branch that tracks a remote one with a different name. To do this, you
|
|
use a syntax for the branch to push that specifies the local branch to push
|
|
and the remote one to push to:
|
|
@pre{$ git push origin my-branch:different-branch --set-upstream
|
|
To pltgit:eli/foo
|
|
* [new branch] my-branch -> different-branch
|
|
Branch my-branch set up to track remote branch different-branch from @;
|
|
origin.}
|
|
@~
|
|
Finally, note that git stores the upstream information in the
|
|
repository-local configuration file. If we look at it now, we will see the
|
|
various upstreams that we have set:
|
|
@pre{$ cat .git/config
|
|
@i{...}
|
|
[remote "origin"]
|
|
fetch = +refs/heads/*:refs/remotes/origin/*
|
|
url = pltgit:eli/foo
|
|
[branch "master"]
|
|
remote = origin
|
|
merge = refs/heads/master
|
|
@i{...}}
|
|
this is the upstream that was made by default when we first checked out our
|
|
clone, together with the information of where the @cmd{origin} repository is.
|
|
Following that are the ones we've setup later:
|
|
@pre{@i{...}
|
|
[branch "b1"]
|
|
remote = .
|
|
merge = refs/heads/master
|
|
[branch "b2"]
|
|
remote = .
|
|
merge = refs/heads/master
|
|
[branch "post-rename"]
|
|
remote = .
|
|
merge = refs/heads/master
|
|
[branch "improve-bar"]
|
|
remote = .
|
|
merge = refs/heads/master
|
|
[branch "my-branch"]
|
|
remote = origin
|
|
merge = refs/heads/different-branch}
|
|
@~
|
|
Note that there are branches that track local branches (ones with a
|
|
@cmd{remote = .} setting), and ones that track remote ones; and also note
|
|
that the @cmd{my-branch} branch tracks a remote branch with a different name.
|
|
Since the settings are stored as configurations, it is possible to inspect
|
|
and change them using @cmd{git config}, or even edit the config file
|
|
directly.
|
|
@pre{$ git config branch.my-branch.remote
|
|
origin
|
|
$ git config branch.my-branch.merge
|
|
refs/heads/different-branch}}
|
|
|
|
@subsection{Managing remotes}
|
|
@p*{
|
|
The distributed nature of git means that you can interact with multiple
|
|
remote repositories. You could have work done with other people done
|
|
locally, where people push/pull from each other's clones (possibly by sending
|
|
around patches, as described below), and eventually when the changes are
|
|
ready push them back to the main repository. You can even have your
|
|
repository track multiple unrelated remote repositories, essentially giving
|
|
you branches that have @em{unrelated} histories.
|
|
@~
|
|
By default, when you clone a remote repository git names it @cmd{origin} —
|
|
and that name appears in many places, most notably in remote branch names.
|
|
As seen in the above config, git remember where the origin repository is via
|
|
a configuration:
|
|
@pre{$ git config remote.origin.url
|
|
pltgit:eli/foo
|
|
$ git config remote.origin.fetch
|
|
+refs/heads/*:refs/remotes/origin/*}
|
|
The first one is the url of the remote repository, and the second one is
|
|
which branches we want to get from it. As with branches, you can use
|
|
@cmd{git config} to change this information, or you can edit the file
|
|
directly, but there is a command that does this more conveniently, keeping
|
|
things consistent:
|
|
@pre{$ git remote # lists all known remotes
|
|
origin
|
|
$ git remote -v # remotes and push/pull specs
|
|
origin pltgit:eli/foo (fetch)
|
|
origin pltgit:eli/foo (push)
|
|
$ git remote show origin # see a detailed description
|
|
* remote origin
|
|
Fetch URL: pltgit:eli/foo
|
|
Push URL: pltgit:eli/foo
|
|
HEAD branch: master
|
|
Remote branches:
|
|
different-branch tracked
|
|
master tracked
|
|
my-branch tracked
|
|
Local branches configured for 'git pull':
|
|
master merges with remote master
|
|
my-branch merges with remote different-branch
|
|
Local refs configured for 'git push':
|
|
master pushes to master (fast-forwardable)
|
|
my-branch pushes to my-branch (up to date)}
|
|
@~
|
|
The @cmd{git remote show} variant will actually query the remote repository
|
|
for its state (using @cmd{git ls-remote}) by default, and tell you when a
|
|
local branch that tracks a remote one is out-of-date.
|
|
@~
|
|
There are a few more sub-verbs for the @cmd{git remote} command which you can
|
|
see on the @man{git-remote} man page, the most important one is for adding a
|
|
remote: @cmd{git remote add @i{short-name} @i{url}}. This is especially
|
|
convenient if you want to have a fork of the @cmd{plt} repository, with most
|
|
interaction happening against it, but occasionally pull/push updates from/to
|
|
the main repository.
|
|
@~
|
|
Of course, remember that you don't need to add remotes to push and pull from
|
|
them. You could do the same by explicitly specifying a url for the
|
|
repository you want to interact with. For example, you could have
|
|
repositories in different accounts on different machines, and synchronize
|
|
your work between them by pushing and pulling directly from one repository to
|
|
another. (Reminder: if you do this, then you're likely to have “checkpoint
|
|
commits” — when you're done with the work, you can do an interactive rebase,
|
|
and squash these checkpoint changes back into logical commit.) But if you do
|
|
this often enough, you will likely find it more convenient to add a named
|
|
remote.}
|
|
|
|
@subsection{Using private repositories}
|
|
@p*{
|
|
A particularly useful use-case for adding a new remote is when you want to
|
|
have private work done in your own fork of the @cmd{plt} repository. Such a
|
|
mode of work is not strictly necessary — you could just do your work in your
|
|
repository in a long-lived branch, but there are certain cases where working
|
|
with a private repository on the server might be more convenient. For
|
|
example, you might want to collaborate with someone else (that has access)
|
|
via the server, or you might use a private fork of the @cmd{plt} repository
|
|
as a central point for synchronizing work from clones on different
|
|
filesystems as described at the end of the last section (the difference from
|
|
that is that you basically use the PLT server as your synchronization point).
|
|
Other than having the main repository reside on the PLT server, working with
|
|
a private repository is not different than working with any other repository.
|
|
@~
|
|
There are two facts that are worth reminding when you deal with a private
|
|
repository. First, remember that creating a private fork is cheap: creating
|
|
a new git clone of a repository will use hard links to the repository store
|
|
object, most of which will be contained in large packed files. The cost in
|
|
terms of space and time for creating a new clone is therefore minimal when
|
|
done on the same filesystem — and using the gitolite @cmd{fork} command is
|
|
doing just that. Please use the @cmd{fork} command to create a private clone
|
|
— gitolite has a feature where it creates any repository that you refer to
|
|
(as long as it has a name that you're allowed to create — starts with your
|
|
username); this means that you could clone the main @cmd{plt} repository and
|
|
push from it into a private repository that doesn't exist: it will be
|
|
created, but such a copy will not share storage with the main repository — it
|
|
will require a new copy, and it will be slow to create.
|
|
@~
|
|
The second thing to remember is that due to the nature of the git store, any
|
|
object, including commits, is stored exactly once. Since commits contain
|
|
their parents, having a specific commit means that you have its complete
|
|
history — therefore, pulling in any branch from any repository will always
|
|
require getting only commits that you don't already have. As a result,
|
|
pulling and pushing to/from any repository will be efficient and move around
|
|
only those commits that are missing on the other side.
|
|
@~
|
|
You can choose one of two basic approaches to working with a private fork:
|
|
you can have the public repository cloned but have branches pushed to your
|
|
private one, or you can have your private fork cloned and occasionally push
|
|
updates to the public one. A way to use these two approaches are described
|
|
and explained now. These examples use the @cmd{play} repository as an
|
|
example (which you are encouraged to experiment with). Note that you can use
|
|
a hybrid approach: you can think about a repository as a container for commit
|
|
histories, pushing and pulling from any other repository, including the copy
|
|
you're working with, the main @cmd{plt} repository, or a private fork (your
|
|
own or another). Note also that since forks are cheap, you can keep several
|
|
of them around, for example, you can have a fork for each long-lived branch —
|
|
it's up to you to settle on a layout that is convenient for your work.}
|
|
@h3{Using a clone of the public repository, pushing branches to your private
|
|
one:}
|
|
@ol*{
|
|
@~ Setting up:
|
|
@ul*{@~ Create a fork:
|
|
@pre{ssh pltgit fork play $user/play}
|
|
@~ Get a local copy of the main repository:
|
|
@pre{git clone pltgit:play}
|
|
(or continue working in an existing one)
|
|
@~ Set up a convenient name for your private repository:
|
|
@pre{cd play
|
|
git remote add my-fork pltgit:$user/play}}
|
|
@~ To start working on a private branch, create one, and push it to your
|
|
private repository:
|
|
@pre{git checkout -b my-branch}
|
|
Then use @cmd{push} to create this branch in your private repository, with
|
|
@cmd{--set-upstream} so git will remember this setting:
|
|
@pre{git push --set-upstream my-fork my-branch}
|
|
You can also have your branch named differently in your fork, for example:
|
|
@pre{git push --set-upstream my-fork my-branch:master}
|
|
will save your branch as the @cmd{master} branch in your fork. This might
|
|
be convenient if you want to clone your private repository elsewhere and
|
|
work only on this branch.
|
|
@~ You can now work as usual in your repository, pushing/pulling changes
|
|
to/from the master branch will go to the public repository, and doing so
|
|
from @cmd{my-branch} will go to your private fork. You can merge changes on
|
|
the @cmd{master} branch to your private one, or rebase your branch onto it.
|
|
However, note that the server will not allow pushing a rebased history to
|
|
your clone. (More details at the end of this section.) You can bypass that
|
|
by pushing to a new branch while keeping your local branch name:
|
|
@pre{git push --set-upstream my-fork my-branch:my-branch-2}
|
|
@~ When you're done merge your branch (possibly rebasing it first) to the
|
|
master branch, and push as usual.
|
|
@~ If you want to delete branches on your fork (either because you pushed a
|
|
rebased version under a new name, or because you're done with that line of
|
|
work), use
|
|
@pre{git push my-fork :my-branch}
|
|
Using an empty branch name for the local branch that you push is the way to
|
|
delete remote branches. (As with local branches, this might lead to losing
|
|
commits, so be careful. As always, git has a few safety mechanisms in
|
|
place, so even if did this by mistake, it is very likely recoverable.)}
|
|
@h3{Using a clone of your private repository, pushing changes to the public
|
|
one:}
|
|
@ol*{
|
|
@~ Setting up:
|
|
@ul*{@~ Create a fork:
|
|
@pre{ssh pltgit fork play $user/play}
|
|
@~ Get a local copy:
|
|
@pre{git clone pltgit:$user/play}
|
|
@~ Set up a convenient name for the main repository:
|
|
@pre{cd play
|
|
git remote add -t master main pltgit:play}
|
|
(@cmd{-t master} tells git to have only the @cmd{master} branch
|
|
retrieved.)}
|
|
@~ Now you can work in this repository as usual — edit, commit, push, etc.
|
|
@~ To push changes to the main repository, first make sure that you're on the
|
|
branch with the changes that you want to push, and then:
|
|
@ul*{@~ Get the recent tree from the main repository
|
|
@pre{git fetch main}
|
|
@~ Rebase or merge your changes with this:
|
|
@pre{git rebase main/master}
|
|
or
|
|
@pre{git merge main/master}
|
|
@~ Push the changes back:
|
|
@pre{git push main}}
|
|
Note that rebasing your branch on top of main/master means that it will be
|
|
rewritten, which means that you will not be able to push your branch back to
|
|
your clone. This is because rewritten histories are currently forbidden by
|
|
the configuration, but this will probably change in the future. Still, even
|
|
if the server would allow pushing a rebased history it (you will need to use
|
|
@cmd{-f} to force such a push), you would need to deal with the rebased
|
|
branch in other clones you might have. Because of this, a rebase is fine if
|
|
you're done with the work that you're pushing, otherwise, a merge is more
|
|
convenient.}
|
|
|
|
@section{Collaborating with others}
|
|
@p*{
|
|
Git makes it very easy to collaborate with anyone, anywhere. You should
|
|
think about repositories as being parts a network which can be synced in any
|
|
topology that is convenient for you. In the case of the PLT repository, the
|
|
main repository on the git server is the central point where the official
|
|
repository lives, and people who can push are directly syncing content into
|
|
it. People who cannot push directly do so through someone who can, by
|
|
sending out patches or “pull requests”. The same applies for any repository,
|
|
of course, including private repositories, even ones that you maintain
|
|
yourself independently of the PLT server.
|
|
@~
|
|
In the case of a patch-based workflow, the two sides that are involved are
|
|
the patch author, and the receiver that integrates it into his/her own clone
|
|
(and from there it goes to the main repository as usual). The work that each
|
|
side does is described in the next two subsections.
|
|
@~
|
|
Following that there is a description for making your repository public,
|
|
which you will need if you're working on a private repository, but it is also
|
|
useful for your collaborator to do so you can use a pull-request workflow.
|
|
In this mode there is no need to email patches; instead, both people make
|
|
their repositories readable to each other, and when some work is ready on one
|
|
person's repository, the other pulls the commits. This is described in the
|
|
last subsection.}
|
|
|
|
@subsection{Patch-based workflow@br
|
|
— instructions for the patch sender side}
|
|
@h4{Executive summary:}
|
|
@ol*{@~ Work in a @cmd{plt} repository clone (possibly in your own branch)
|
|
@~ @npre{$ git send-email origin/master}
|
|
@~ You're done — thanks!
|
|
@~ When the patch is applied, you will get the changes through
|
|
@cmd{origin/master}, so if you worked on your master branch, make sure
|
|
to use @cmd{git pull --rebase} which will notice that your changes were
|
|
applied; if you worked on a branch, then you can now delete it (the
|
|
commit objects will be different from the ones you've made).}
|
|
@h4{Longer version:}
|
|
@ol*{
|
|
@~ Work & commit as usual. In general, it is a good idea to use
|
|
@cmd{Signed-off-by: @i{Your Name} <@i|{your@email}|>} in commit messages,
|
|
which is a conventional way to declare that you agree for your work to be
|
|
released as part of the PLT project, under the terms of the LGPL.
|
|
@cmd{git commit} will add that for you if you use the @cmd{-s} flag. You
|
|
can also make git do this later, when you send the patches out.
|
|
@~ Make sure that you're working with a relatively recent clone, and that
|
|
you're on the branch where you did your work. In most cases, this would be
|
|
the master branch, but you can do your work in your own branch too, of
|
|
course.
|
|
@~ @p*{
|
|
Verify that your commits are all in your history. You can see the commits
|
|
that you have over the @cmd{plt} history with
|
|
@pre{git log --oneline origin/master..}
|
|
these are the commits that you're going to send over now. (You can use
|
|
the usual git toolset to tweak them further, or specify only some commits,
|
|
etc.)
|
|
@~
|
|
A relevant point to consider here is that git takes the first paragraph of
|
|
each commit message as a subject line. When sending out a patch, this is
|
|
made concrete by actually using it as the emails's subject — so it is a
|
|
good idea to make sure that this log looks fine, since the @cmd{--oneline}
|
|
option will make it show those subjects.
|
|
@~
|
|
Obviously, you should also make sure that the commits have clear
|
|
descriptions of your work. People who in the core group often have some
|
|
general context that they are aware of, so some commit messages can be
|
|
cryptic or even worse (eg, you might find @cmd{.} as a commit message) —
|
|
don't mimic this... As a more occasional contributor, you should explain
|
|
your work in more details. (There's no policy on commit messages, but you
|
|
do need to go through some person on the team.)}
|
|
@~ @p*{
|
|
At this point you should decide how to send your patches. Emailing them
|
|
is be the most convenient way to do this — to do this, you would use the
|
|
@cmd{send-email} command:
|
|
@pre{git send-email origin/master..}
|
|
or if you send only some commits, use a different specification. To make
|
|
things even easier, a single commit specification is considered as the
|
|
starting point and all of the following commits (up to your branch's tip)
|
|
will be included in the emails (in contrast to other git commands like
|
|
@cmd{log}, where a single commit name is considered as the set of commits
|
|
leading up to it) — so you can do this:
|
|
@pre{git send-email origin/master}
|
|
This will ask you a bunch of questions — it's easy to answer but you can
|
|
also specify them as command-line options. if you intend to do this
|
|
frequently it might be a good idea to make it easier with some settings in
|
|
your global .gitconfig file. For example, I have these settings:
|
|
@pre|{[sendemail]
|
|
from = Eli Barzilay <eli@barzilay.org>
|
|
bcc = eli@barzilay.org
|
|
suppresscc = self}|
|
|
and you can see more in the @man{git-config} and @man{git-send-email} man
|
|
pages. The address to send the patches to is also configurable — you can
|
|
use something like
|
|
@pre{to = dev@at-racket}
|
|
or
|
|
@pre{to = someone@at-racket}
|
|
depending on who you send your patches to — but this is better done as a
|
|
repository-local configuration option (or just use the @cmd{--to} flag).
|
|
@~
|
|
You can add a @cmd{-s} flag to the command, to make git add
|
|
@cmd{Signed-off-by} lines to commit messages. (See above for what this
|
|
means.)
|
|
@~
|
|
If you want to send the files in some other way (eg, send them all
|
|
packaged in an archive as attachment[*]), then just use @cmd{format-patch}
|
|
instead of @cmd{send-email} — git will create a number of patch files in
|
|
your current directory, which will be named @path{NNNN-text.patch} where
|
|
the text is made out of the subject lines of the commit messages (the
|
|
first line). You can even run
|
|
@pre{git format-patch origin/master --stdout > my-patch}
|
|
to concatenate them all and send the resulting file over.
|
|
@~
|
|
@small{[*] Note that doing this means that it is not as easy to read your
|
|
patch, so avoid doing this if you want to make it easier to read and
|
|
accept it. On the other hand, if you're working with someone specific,
|
|
they might prefer attachments (for example, it's easier to save the
|
|
attached file from gmail).}}
|
|
@~ Once the commits have been pushed to the main repository, you would get
|
|
them when you pull to update. The commits will now be different objects
|
|
than the ones you have — since the information changed (at least the
|
|
committer information will be different, the log message might have been
|
|
edited, etc). If you made your commits on your master branch which is set
|
|
to track the @cmd{plt} master branch (the usual setup), then make sure that
|
|
you run @cmd{git pull --rebase} to update — this will identify the commits
|
|
as already included and will not include them in the rebased master. But if
|
|
you made your commits in a private branch, and assuming that you didn't do
|
|
any additional work there, then you can now just delete that branch. (If
|
|
you did do more work there, then you should rebase it, to avoid resending
|
|
the same patches again.)}
|
|
|
|
@subsection{Patch-based workflow@br
|
|
— instructions for the patch receiver side}
|
|
@p*{
|
|
Accepting patches that were sent via email (on any other way), is also
|
|
simple. The command to do this is @cmd{git am}, which expects an argument
|
|
that is a mailbox file holding the patch emails, or you could run it and pipe
|
|
a patch email into it.}
|
|
@h4{Executive summary:}
|
|
@ol*{@~ @npre{$ git checkout master}
|
|
@~ Save (unmodified) patch emails into a mail folder file.
|
|
@~ @npre{$ git am -3 @i{the-mail-folder}}
|
|
@~ Push the changes up to the server}
|
|
@h4{Longer version:}
|
|
@ol*{
|
|
@~ While you will not be author of the commits, you will be their committed, so
|
|
you should of course be aware of the changes, and be willing to maintain the
|
|
new code and other work that is implied. So the first step that you should
|
|
do is review the patch and make sure that you are willing to accept
|
|
responsibility for it.
|
|
@~ Save the patch emails to a mail folder (usually a file). You must take care
|
|
to save the emails @em{as is}, including the date, author, and subject
|
|
headers, and avoiding text that could have been butchered by your email
|
|
client. For example, if you're using gmail, then use the “show original”
|
|
option to view the raw email text, and save that to a file (even in this
|
|
format gmail will have a first line with a bunch of spaces — it's best to
|
|
remove that). Otherwise, gmail does things like wrap lines, replaces spaces
|
|
by non-breaking spaces, or remove spaces. Alternatively, extract patch
|
|
files from an archive if that's what you received, or save a single
|
|
attachment file etc.
|
|
@~ In your repository clone, make sure that you're on the branch that you want
|
|
to integrate the changes into. You could do this in your master branch, or
|
|
in a new topic branch (especially if there is more than one patch).
|
|
@~ Run @cmd{git am -3 mail-folder} (@cmd{am} stands for “apply-mail”) with the
|
|
mail folder that you've created above. It will apply the patches and commit
|
|
them one by one. Like @cmd{git rebase}, if there are conflicts the process
|
|
will stop so you can resolve it — and then run the @cmd{am} command with
|
|
@cmd{--continue}, or @cmd{--skip} this commit and continue with the rest, or
|
|
@cmd{--abort} to go back to the start. The @cmd{-3} flags tells git that if
|
|
a conflicting patch comes from the above @cmd{format-patch}, and it
|
|
specifies files that we have, then try a 3-way merge — this will make things
|
|
generally better (and it can identify more patches that were applied,
|
|
instead of showing them as conflicts).
|
|
@br
|
|
You can also use an @cmd{-i} flag to the command to get an interactive
|
|
version — for each commit it will ask you what to do with it, and let you
|
|
edit the log message.
|
|
@~ Finally push the commits as usual.}
|
|
|
|
@subsection{Making a private repository publicly available}
|
|
@p*{
|
|
If you're working with “outside people” (people with no accounts on the PLT
|
|
server, and no direct file-system access etc) on a private repository, you
|
|
will need to find some way to make your repository available for cloning. An
|
|
easy way to do so is to put it on a filesystem that those people can access —
|
|
eg, if you're all in the same department. Another easy way to make a
|
|
repository available is to find a hosting service like github and others —
|
|
there are many options here, some are free but limited, and some cost money;
|
|
if you prefer this easy solution, keep in mind that you can pay for the
|
|
duration of the collaboration and at the end you can simply keep your
|
|
repository clone to yourself (eg, if you're working on a paper then there's
|
|
no need to pay once all work is done).
|
|
@~
|
|
But if you want to do it yourself, the quickest and most convenient way to
|
|
make a repository public is to put it in a directory that is available on the
|
|
web. Such repositories can be cloned directly from the URL the repository is
|
|
available at — there's no need to setup a server in a special way, and no
|
|
need to run cgi scripts.}
|
|
@h4{Executive summary:}
|
|
@ol*{@~ @npre{$ git clone --bare @i{your-repo} ~/public_html/@i{repo}.git}
|
|
@~ @npre{$ cd ~/public_html/@i{repo}.git/hooks;
|
|
mv post-update.sample post-update;
|
|
chmod +x post-update}
|
|
@~ @npre{$ git remote add public ~/public_html/@i{repo}.git}
|
|
@~ Tell people to clone from @cmd{http://some.where/~you/@i{repo}.git}
|
|
@~ Work, apply email patches, and: @cmd{git push public}}
|
|
@h4{Longer version:}
|
|
@ol*{
|
|
@~ Make a “bare” repository — this is a repository that has no working
|
|
directory:
|
|
@pre{git clone --bare @i{your-repo} @i{repo}.git}
|
|
This will create a @path{@i{repo}.git} directory holding the bare
|
|
repository. You should use some path in a directory where you have web
|
|
pages published.
|
|
@~ The URL where the directory is found at is what other people should use when
|
|
cloning.
|
|
@~ You can now push to this repository, and other people will see it too. To
|
|
make things easier, you can set a remote name for this repository, so it's
|
|
easy for you to push changes to it.
|
|
@pre{git remote add public ~/public_html/@i{repo}.git}
|
|
And now you can use @cmd{git push public}. (You can also pull from it, but
|
|
since you're going to be the only one who pushes into it, that will not be
|
|
necessary.)
|
|
@~ One thing to be aware of is that while a repository can be published through
|
|
HTTP this way, git considers that a “dumb protocol” (because there is no
|
|
proper interaction between the two sides). To still make cloning possible,
|
|
you will need to maintain some meta-files that hold entry points to the
|
|
objects in your repository — to get this, run:
|
|
@pre{git update-server-info}
|
|
You need to run this after each update to the repository — and to automate
|
|
this you can have a hook do it for you. In the bare repository you will
|
|
find a @path{hooks} directory with a file called @path{post-update.sample} —
|
|
simply rename this file to @path{post-update}, and make it executable with
|
|
@cmd{chmod +x post-update}. From now on every push to this repository will
|
|
run the hook and keep the meta files updated.}
|
|
|
|
@subsection{Pull-request workflow}
|
|
@p*{
|
|
A possibly easier way for people to contribute work is to make their
|
|
repositories available somehow. In the case of a private repository, the two
|
|
sides can be in a shared file system, with read permissions for each other;
|
|
or achieved as described in the previous subsection. In the case of
|
|
contributing to the @cmd{plt} repository, the contributor can maintain a
|
|
public fork of the @cmd{plt} repository (eg, by forking the @cmd{plt} github
|
|
mirror at @selflink{https://github.com/plt/racket} directly on github).
|
|
@~
|
|
In this workflow there is no need to mail patches — instead, the receiver
|
|
simply pulls them directly from the sender's repository. For example,
|
|
someone tells you that they have some new commits in a @cmd{foo} branch of
|
|
their repository. Since this is a repository that you can access, and since
|
|
it shares history with yours, you can just pull that branch in, for example:
|
|
@pre{git checkout -b someones-work
|
|
git pull @i{someones-repository-url}}
|
|
Note that the @cmd{pull} will merge the changes, creating a merge
|
|
commit if your @cmd{master} branch cannot be fast-forwarded. To avoid
|
|
this, you can use @cmd{fetch} instead:
|
|
@pre{git checkout -b someones-work
|
|
git fetch @i{someones-repository-url}}
|
|
Either way, this fetches the remote repository's HEAD. You can create the
|
|
branch in a single fetch command by specifying the remote branch name, and
|
|
the local branch to fetch into, for example:
|
|
@pre{git fetch @i{someones-repository-url} master:someone}
|
|
@~
|
|
If you expect to do this often (eg, you're going to suggest fixes for the
|
|
work and get new work in), then you can add a @cmd{someone} remote to be used
|
|
more conveniently:
|
|
@pre{git remote add someone @i{someones-repository-url}
|
|
git fetch someone
|
|
git checkout -b some-branch someone/some-branch}
|
|
possibly using -t to make the branch track the remote one:
|
|
@pre{git checkout -tb some-branch someone/some-branch}
|
|
Note that there is no need to create a branch before the @cmd{fetch}, since
|
|
it will be fetched to a @cmd{remotes/someone/master} branch.
|
|
@~
|
|
Once you pulled in the branch, you can inspect the changes, merge them,
|
|
rebase them, etc. The important point here is that you have a copy of the
|
|
contributed line of work, which you can use with the usual git toolset.
|
|
@~
|
|
When/if you're happy with the changes, you can simply integrate them to your
|
|
master branch, and if this is in a clone of the @cmd{plt} repository, then at
|
|
this point you can simply push these commits to the main server. Once that
|
|
happens, the contributor can update their own clone, and continue working as
|
|
usual.
|
|
@~
|
|
Git has a tool that makes this mode of work a little more organized and
|
|
robust for the contributor: @cmd{git request-pull}. This simple command
|
|
(surprisingly, it has no flags) expects a commit that marks the start of the
|
|
new work (actually, the last one before it, eg, @cmd{origin/master}), and the
|
|
url of the repository. For example:
|
|
@pre{git request-pull origin git://github.com/someone/somefork.git}
|
|
@~
|
|
Of course, the contributor doesn't have to work directly in the available
|
|
repository — in the case of github or with an over-the-web setup like the one
|
|
described in the previous subsection the public repository is a bare one, and
|
|
no work can be done directly on it. So what actually happens is: the
|
|
contributor works on his/her own repository, pushes changes to the public
|
|
one, and then requests a pull.
|
|
@~
|
|
The @cmd{request-pull} command will therefore check that the new commits are
|
|
indeed available at that location, and find out the branch that they're on
|
|
(in case it's different than the branch that someone is working on). It then
|
|
prints out a “pull request” text with a description of the changes, the url
|
|
that was specified, the branch name with the new work, and a summary of the
|
|
files that were changed. In short, all the relevant information is there,
|
|
and it even verified that the commits are indeed available — merging them in
|
|
is now easy.
|
|
@~
|
|
(As a sidenote, you can use @cmd{.} as the url:
|
|
@cmd{git request-pull origin .}, and get a condensed summary of your
|
|
changes.)}
|
|
|
|
@subsection{Pull-request workflow@br
|
|
— recipe for the sender side}
|
|
@ol*{@~ Clone the @cmd{plt} repository and work with it as usual, commit your
|
|
work
|
|
@~ Make your repository publicly available
|
|
@~ @npre{$ git request-pull origin @i{your-repository-url}}
|
|
@~ Send the resulting text to @cmd{dev@at-racket}
|
|
@~ You're done — thanks!}
|
|
@p{Alternatively, you can fork the @cmd{plt} repository on github:
|
|
@cmd{https://github.com/plt/racket}, commit, then do a pull request. Note:
|
|
it is better to send a note about your pull request to @cmd{dev@at-racket},
|
|
or you can do the pull request directly with git as listed above (using
|
|
github to have a public repository).}
|
|
|
|
@subsection{Pull-request workflow@br
|
|
— recipe for the receiver side}
|
|
@p{This recipe is for getting some remote work in as a one-time job. If you
|
|
need to cooperate more closely with someone, you will want to add the remote
|
|
repository with @cmd{git remote} as shown above.}
|
|
@ol*{
|
|
@~ Get a @cmd{plt} clone, or use your own (it's safe to do the latter, no need
|
|
for a new clone unless you're paranoid):
|
|
@pre{git clone pltgit:plt
|
|
cd plt}
|
|
@~ Get the foreign repository's master branch (or any other branch) into a
|
|
local branch:
|
|
@pre{git fetch @i{remote-repository-url} master:foo}
|
|
This pulls the @cmd{master} branch of the remote repository into a local
|
|
@cmd{foo} branch (you can use other names, of course).
|
|
@~ Inspect the changes as usual
|
|
@pre{git log master..foo # new commits
|
|
git diff master...foo # changes
|
|
git log -p master..foo # both}
|
|
(See above for more details on these.)
|
|
@~ If you're happy with the change and want to get it as-is, you can simply
|
|
@cmd{merge} the branch:
|
|
@pre{git merge foo}
|
|
But unless the remote work was done from the point your @cmd{master} points
|
|
at (i.e., there were no new commits), this will generate a merge commit that
|
|
might not be desired. To avoid it, you can rebase the branch against your
|
|
@cmd{master} and then do the @cmd{merge} (which will now be a fast-forward)
|
|
merge:
|
|
@pre{git checkout foo
|
|
git rebase master
|
|
git checkout master
|
|
git merge foo}
|
|
@~ You no longer need the @cmd{foo} branch, so delete it with:
|
|
@pre{git branch -d foo}
|
|
@~ Push things back as usual}
|
|
|
|
@subsection{Merging github pull-requests}
|
|
@p*{
|
|
Github is popular enough that some people prefer to work with a github fork
|
|
of the PLT repository, and then send a pull request. Merging these pull
|
|
requests can be done as with any other repository, as explained in the
|
|
previous section. However, with github there is an easy way to deal with
|
|
it.
|
|
@~
|
|
A pull request has a URL like @cmd{https://github.com/plt/racket/pull/123}
|
|
which you can use in your browser to inspect the changes. To apply the
|
|
changes locally, a convenient feature is that you can add a @cmd{.patch}
|
|
suffix to every pull request URL which will have a text version of the patch.
|
|
This means that applying the patch is particularly easy on the command line,
|
|
for example:
|
|
@pre{curl https://github.com/plt/racket/pull/123.patch | git am}
|
|
will fetch the patch text and apply it (and you can now push as usual, or
|
|
locally inspect the ptach and possibly edit it in the usual ways).}
|
|
|
|
@section{Additional Resources}
|
|
@dl*{
|
|
@~ @strong{Quick and short:}
|
|
@~ @dl*{
|
|
@~ @selflink{http://eagain.net/articles/git-for-computer-scientists/}
|
|
@~ Basic description of what makes a git repository
|
|
@~ Cheat sheets:
|
|
@~ @dl*{
|
|
@~ @selflink{http://gitref.org/}
|
|
@~ Quick reference thing, with links to the git man pages and the progit
|
|
book
|
|
@~ @selflink{http://jonas.nitro.dk/git/quick-reference.html}
|
|
@~ Really short
|
|
@~ @selflink{http://cheat.errtheblog.com/s/git}
|
|
@~ Explains some more
|
|
@~ @selflink{http://ktown.kde.org/~zrusin/git/git-cheat-sheet.svg}
|
|
@~ Short, intended for printing
|
|
@~ @selflink{http://help.github.com/git-cheat-sheets/}
|
|
@~ Similar}
|
|
@~ @selflink{http://git.or.cz/course/svn.html}
|
|
@~ subversion->git crash course
|
|
@~ @selflink{http://www.kernel.org/pub/software/scm/git/docs/everyday.html}
|
|
@~ Nice summary of a few things, but too verbose or too advanced in some
|
|
places, and also a little outdated.}
|
|
@~ @strong{Books:}
|
|
@~ @dl*{
|
|
@~ @selflink{http://book.git-scm.com/}
|
|
@~ The git community book. Also, there are a bunch of videos
|
|
linked, and some tutorial links in the “Welcome” part.
|
|
@~ @selflink{http://progit.org/book/}
|
|
@~ A frequently recommended book. (Also some good blog entries.)
|
|
@~ @selflink{http://www-cs-students.stanford.edu/~blynn/gitmagic/}
|
|
@~ Another good book (a bit more verbose than the previous one)}
|
|
@~ @strong{Misc:}
|
|
@~ @dl*{
|
|
@~ @selflink{http://www.kernel.org/pub/software/scm/git/docs/@;
|
|
gittutorial.html}
|
|
@~ The git tutorial, also available as the @man{gittutorial} man page.
|
|
@~ @selflink{http://help.github.com/}
|
|
@~ Some github guides, well-organized by levels.
|
|
@~ @selflink{http://www.gitready.com/}
|
|
@~ A kind of a collection of small tips; didn't change in a while though.
|
|
@~ @selflink{http://marklodato.github.com/visual-git-guide/}
|
|
@~ This is a short visual document about git. But it goes a little fast, so
|
|
it would be useful after you're comfortable with the basics.}}
|
|
|
|
}}))
|