Merge branches 'bf/doc' and 'db/tartree'

This commit is contained in:
Junio C Hamano 2006-01-30 22:41:00 -08:00
commit 1a5c3a01aa
3 changed files with 176 additions and 267 deletions

View file

@ -1623,123 +1623,7 @@ suggested in the previous section may be new to you. You do not
have to worry. git supports "shared public repository" style of
cooperation you are probably more familiar with as well.
For this, set up a public repository on a machine that is
reachable via SSH by people with "commit privileges". Put the
committers in the same user group and make the repository
writable by that group. Make sure their umasks are set up to
allow group members to write into directories other members
have created.
You, as an individual committer, then:
- First clone the shared repository to a local repository:
------------------------------------------------
$ git clone repo.shared.xz:/pub/scm/project.git/ my-project
$ cd my-project
$ hack away
------------------------------------------------
- Merge the work others might have done while you were hacking
away:
------------------------------------------------
$ git pull origin
$ test the merge result
------------------------------------------------
[NOTE]
================================
The first `git clone` would have placed the following in
`my-project/.git/remotes/origin` file, and that's why this and
the next step work.
------------
URL: repo.shared.xz:/pub/scm/project.git/ my-project
Pull: master:origin
------------
================================
- push your work as the new head of the shared
repository.
------------------------------------------------
$ git push origin master
------------------------------------------------
If somebody else pushed into the same shared repository while
you were working locally, `git push` in the last step would
complain, telling you that the remote `master` head does not
fast forward. You need to pull and merge those other changes
back before you push your work when it happens.
The `git push` command without any explicit refspec parameter
pushes the refs that exist both in the local repository and the
remote repository. So the last `push` can be done with either
one of these:
------------
$ git push origin
$ git push repo.shared.xz:/pub/scm/project.git/
------------
as long as the shared repository does not have any branches
other than `master`.
[NOTE]
============
If you created your shared repository by cloning from somewhere
else, you may have the `origin` branch. Your developers
typically do not use that branch; remove it. Otherwise, that
would be pushed back by the `git push origin` because your
developers' repository would surely have `origin` branch to keep
track of the shared repository, and would be counted as "exist
on both ends".
============
Advanced Shared Repository Management
-------------------------------------
Being able to push into a shared repository means being able to
write into it. If your developers are coming over the network,
this means you, as the repository administrator, need to give
each of them an SSH access to the shared repository machine.
In some cases, though, you may not want to give a normal shell
account to them, but want to restrict them to be able to only
do `git push` into the repository and nothing else.
You can achieve this by setting the login shell of your
developers on the shared repository host to `git-shell` program.
[NOTE]
Most likely you would also need to list `git-shell` program in
`/etc/shells` file.
This restricts the set of commands that can be run from incoming
SSH connection for these users to only `receive-pack` and
`upload-pack`, so the only thing they can do are `git fetch` and
`git push`.
You still need to create UNIX user accounts for each developer,
and put them in the same group. Make sure that the repository
shared among these developers is writable by that group.
. Initializing the shared repository with `git-init-db --shared`
helps somewhat.
. Run the following in the shared repository:
+
------------
$ chgrp -R $group repo.git
$ find repo.git -type d -print | xargs chmod ug+rwx,g+s
$ GIT_DIR=repo.git git repo-config core.sharedrepository true
------------
The above measures make sure that directories lazily created in
`$GIT_DIR` are writable by group members. You, as the
repository administrator, are still responsible to make sure
your developers belong to that shared repository group and set
their umask to a value no stricter than 027 (i.e. at least allow
reading and searching by group members).
You can implement finer grained branch policies using update
hooks. There is a document ("control access to branches") in
Documentation/howto by Carl Baldwin and JC outlining how to (1)
limit access to branch per user, (2) forbid overwriting existing
tags.
See link:cvs-migration.txt[git for CVS users] for the details.
Bundling your work together
---------------------------

View file

@ -1,126 +1,182 @@
git for CVS users
=================
Ok, so you're a CVS user. That's ok, it's a treatable condition, and the
first step to recovery is admitting you have a problem. The fact that
you are reading this file means that you may be well on that path
already.
So you're a CVS user. That's ok, it's a treatable condition. The job of
this document is to put you on the road to recovery, by helping you
convert an existing cvs repository to git, and by showing you how to use a
git repository in a cvs-like fashion.
The thing about CVS is that it absolutely sucks as a source control
manager, and you'll thus be happy with almost anything else. git,
however, may be a bit 'too' different (read: "good") for your taste, and
does a lot of things differently.
Some basic familiarity with git is required. This
link:tutorial.html[tutorial introduction to git] should be sufficient.
One particular suckage of CVS is very hard to work around: CVS is
basically a tool for tracking 'file' history, while git is a tool for
tracking 'project' history. This sometimes causes problems if you are
used to doing very strange things in CVS, in particular if you're doing
things like making branches of just a subset of the project. git can't
track that, since git never tracks things on the level of an individual
file, only on the whole project level.
First, note some ways that git differs from CVS:
The good news is that most people don't do that, and in fact most sane
people think it's a bug in CVS that makes it tag (and check in changes)
one file at a time. So most projects you'll ever see will use CVS
'as if' it was sane. In which case you'll find it very easy indeed to
move over to git.
* Commits are atomic and project-wide, not per-file as in CVS.
First off: this is not a git tutorial. See
link:tutorial.html[Documentation/tutorial.txt] for how git
actually works. This is more of a random collection of gotcha's
and notes on converting from CVS to git.
* Offline work is supported: you can make multiple commits locally,
then submit them when you're ready.
Second: CVS has the notion of a "repository" as opposed to the thing
that you're actually working in (your working directory, or your
"checked out tree"). git does not have that notion at all, and all git
working directories 'are' the repositories. However, you can easily
emulate the CVS model by having one special "global repository", which
people can synchronize with. See details later, but in the meantime
just keep in mind that with git, every checked out working tree will
have a full revision control history of its own.
* Branching is fast and easy.
* Every working tree contains a repository with a full copy of the
project history, and no repository is inherently more important than
any other. However, you can emulate the CVS model by designating a
single shared repository which people can synchronize with; see below
for details.
Importing a CVS archive
-----------------------
Ok, you have an old project, and you want to at least give git a chance
to see how it performs. The first thing you want to do (after you've
gone through the git tutorial, and generally familiarized yourself with
how to commit stuff etc in git) is to create a git'ified version of your
CVS archive.
First, install version 2.1 or higher of cvsps from
link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
sure it is in your path. The magic command line is then
Happily, that's very easy indeed. git will do it for you, although git
will need the help of a program called "cvsps":
-------------------------------------------
$ git cvsimport -v -d <cvsroot> -C <destination> <module>
-------------------------------------------
http://www.cobite.com/cvsps/
This puts a git archive of the named CVS module in the directory
<destination>, which will be created if necessary. The -v option makes
the conversion script very chatty.
which is not actually related to git at all, but which makes CVS usage
look almost sane (ie you almost certainly want to have it even if you
decide to stay with CVS). However, git will want 'at least' version 2.1
of cvsps (available at the address above), and in fact will currently
refuse to work with anything else.
The import checks out from CVS every revision of every file. Reportedly
cvsimport can average some twenty revisions per second, so for a
medium-sized project this should not take more than a couple of minutes.
Larger projects or remote repositories may take longer.
Once you've gotten (and installed) cvsps, you may or may not want to get
any more familiar with it, but make sure it is in your path. After that,
the magic command line is
The main trunk is stored in the git branch named `origin`, and additional
CVS branches are stored in git branches with the same names. The most
recent version of the main trunk is also left checked out on the `master`
branch, so you can start adding your own changes right away.
git cvsimport -v -d <cvsroot> -C <destination> <module>
The import is incremental, so if you call it again next month it will
fetch any CVS updates that have been made in the meantime. For this to
work, you must not modify the imported branches; instead, create new
branches for your own changes, and merge in the imported branches as
necessary.
which will do exactly what you'd think it does: it will create a git
archive of the named CVS module. The new archive will be created in the
subdirectory named <destination>; it'll be created if it doesn't exist.
Default is the local directory.
Development Models
------------------
It can take some time to actually do the conversion for a large archive
since it involves checking out from CVS every revision of every file,
and the conversion script is reasonably chatty unless you omit the '-v'
option, but on some not very scientific tests it averaged about twenty
revisions per second, so a medium-sized project should not take more
than a couple of minutes. For larger projects or remote repositories,
the process may take longer.
CVS users are accustomed to giving a group of developers commit access to
a common repository. In the next section we'll explain how to do this
with git. However, the distributed nature of git allows other development
models, and you may want to first consider whether one of them might be a
better fit for your project.
After the (initial) import is done, the CVS archive's current head
revision will be checked out -- thus, you can start adding your own
changes right away.
For example, you can choose a single person to maintain the project's
primary public repository. Other developers then clone this repository
and each work in their own clone. When they have a series of changes that
they're happy with, they ask the maintainer to pull from the branch
containing the changes. The maintainer reviews their changes and pulls
them into the primary repository, which other developers pull from as
necessary to stay coordinated. The Linux kernel and other projects use
variants of this model.
The import is incremental, i.e. if you call it again next month it'll
fetch any CVS updates that have been happening in the meantime. The
cut-off is date-based, so don't change the branches that were imported
from CVS.
With a small group, developers may just pull changes from each other's
repositories without the need for a central maintainer.
You can merge those updates (or, in fact, a different CVS branch) into
your main branch:
Emulating the CVS Development Model
-----------------------------------
git resolve HEAD origin "merge with current CVS HEAD"
Start with an ordinary git working directory containing the project, and
remove the checked-out files, keeping just the bare .git directory:
The HEAD revision from CVS is named "origin", not "HEAD", because git
already uses "HEAD". (If you don't like 'origin', use cvsimport's
'-o' option to change it.)
------------------------------------------------
$ mv project/.git /pub/repo.git
$ rm -r project/
------------------------------------------------
Next, give every team member read/write access to this repository. One
easy way to do this is to give all the team members ssh access to the
machine where the repository is hosted. If you don't want to give them a
full shell on the machine, there is a restricted shell which only allows
users to do git pushes and pulls; see gitlink:git-shell[1].
Emulating CVS behaviour
-----------------------
Put all the committers should in the same group, and make the repository
writable by that group:
------------------------------------------------
$ chgrp -R $group repo.git
$ find repo.git -mindepth 1 -type d |xargs chmod ug+rwx,g+s
$ GIT_DIR=repo.git git repo-config core.sharedrepository true
------------------------------------------------
So, by now you are convinced you absolutely want to work with git, but
at the same time you absolutely have to have a central repository.
Step back and think again. Okay, you still need a single central
repository? There are several ways to go about that:
Make sure committers have a umask of at most 027, so that the directories
they create are writable and searchable by other group members.
1. Designate a person responsible to pull all branches. Make the
repository of this person public, and make every team member
pull regularly from it.
Suppose this repository is now set up in /pub/repo.git on the host
foo.com. Then as an individual commiter you can clone the shared
repository:
2. Set up a public repository with read/write access for every team
member. Use "git pull/push" as you used "cvs update/commit". Be
sure that your repository is up to date before pushing, just
like you used to do with "cvs commit"; your push will fail if
what you are pushing is not up to date.
------------------------------------------------
$ git clone foo.com:/pub/repo.git/ my-project
$ cd my-project
------------------------------------------------
3. Make the repository of every team member public. It is the
responsibility of each single member to pull from every other
team member.
and hack away. The equivalent of `cvs update` is
------------------------------------------------
$ git pull origin
------------------------------------------------
which merges in any work that others might have done since the clone
operation.
[NOTE]
================================
The first `git clone` places the following in the
`my-project/.git/remotes/origin` file, and that's why the previous step
and the next step both work.
------------
URL: foo.com:/pub/project.git/ my-project
Pull: master:origin
------------
================================
You can update the shared repository with your changes using:
------------------------------------------------
$ git push origin master
------------------------------------------------
If someone else has updated the repository more recently, `git push`, like
`cvs commit`, will complain, in which case you must pull any changes
before attempting the push again.
In the `git push` command above we specify the name of the remote branch
to update (`master`). If we leave that out, `git push` tries to update
any branches in the remote repository that have the same name as a branch
in the local repository. So the last `push` can be done with either of:
------------
$ git push origin
$ git push repo.shared.xz:/pub/scm/project.git/
------------
as long as the shared repository does not have any branches
other than `master`.
[NOTE]
============
Because of this behaviour, if the shared repository and the developer's
repository both have branches named `origin`, then a push like the above
attempts to update the `origin` branch in the shared repository from the
developer's `origin` branch. The results may be unexpected, so it's
usually best to remove any branch named `origin` from the shared
repository.
============
Advanced Shared Repository Management
-------------------------------------
Git allows you to specify scripts called "hooks" to be run at certain
points. You can use these, for example, to send all commits to the shared
repository to a mailing list. See link:hooks.txt[Hooks used by git].
You can enforce finer grained permissions using update hooks. See
link:howto/update-hook-example.txt[Controlling access to branches using
update hooks].
CVS annotate
------------

View file

@ -3,6 +3,8 @@
*/
#include <time.h>
#include "cache.h"
#include "tree.h"
#include "commit.h"
#define RECORDSIZE (512)
#define BLOCKSIZE (RECORDSIZE * 20)
@ -334,76 +336,45 @@ static void write_header(const unsigned char *sha1, char typeflag, const char *b
write_if_needed();
}
static void traverse_tree(void *buffer, unsigned long size,
static void traverse_tree(struct tree *tree,
struct path_prefix *prefix)
{
struct path_prefix this_prefix;
struct tree_entry_list *item;
this_prefix.prev = prefix;
while (size) {
int namelen = strlen(buffer)+1;
parse_tree(tree);
item = tree->entries;
while (item) {
void *eltbuf;
char elttype[20];
unsigned long eltsize;
unsigned char *sha1 = buffer + namelen;
char *path = strchr(buffer, ' ') + 1;
unsigned int mode;
if (size < namelen + 20 || sscanf(buffer, "%o", &mode) != 1)
die("corrupt 'tree' file");
if (S_ISDIR(mode) || S_ISREG(mode))
mode |= (mode & 0100) ? 0777 : 0666;
buffer = sha1 + 20;
size -= namelen + 20;
eltbuf = read_sha1_file(sha1, elttype, &eltsize);
eltbuf = read_sha1_file(item->item.any->sha1,
elttype, &eltsize);
if (!eltbuf)
die("cannot read %s", sha1_to_hex(sha1));
write_header(sha1, TYPEFLAG_AUTO, basedir, prefix, path,
mode, eltbuf, eltsize);
if (!strcmp(elttype, "tree")) {
this_prefix.name = path;
traverse_tree(eltbuf, eltsize, &this_prefix);
} else if (!strcmp(elttype, "blob") && !S_ISLNK(mode)) {
die("cannot read %s",
sha1_to_hex(item->item.any->sha1));
write_header(item->item.any->sha1, TYPEFLAG_AUTO, basedir,
prefix, item->name,
item->mode, eltbuf, eltsize);
if (item->directory) {
this_prefix.name = item->name;
traverse_tree(item->item.tree, &this_prefix);
} else if (!item->symlink) {
write_blocked(eltbuf, eltsize);
}
free(eltbuf);
item = item->next;
}
}
/* get commit time from committer line of commit object */
static time_t commit_time(void * buffer, unsigned long size)
{
time_t result = 0;
char *p = buffer;
while (size > 0) {
char *endp = memchr(p, '\n', size);
if (!endp || endp == p)
break;
*endp = '\0';
if (endp - p > 10 && !memcmp(p, "committer ", 10)) {
char *nump = strrchr(p, '>');
if (!nump)
break;
nump++;
result = strtoul(nump, &endp, 10);
if (*endp != ' ')
result = 0;
break;
}
size -= endp - p - 1;
p = endp + 1;
}
return result;
}
int main(int argc, char **argv)
{
unsigned char sha1[20];
unsigned char commit_sha1[20];
void *buffer;
unsigned long size;
struct commit *commit;
struct tree *tree;
setup_git_directory();
@ -419,14 +390,13 @@ int main(int argc, char **argv)
usage(tar_tree_usage);
}
buffer = read_object_with_reference(sha1, "commit", &size, commit_sha1);
if (buffer) {
write_global_extended_header(commit_sha1);
archive_time = commit_time(buffer, size);
free(buffer);
commit = lookup_commit_reference(sha1);
if (commit) {
write_global_extended_header(commit->object.sha1);
archive_time = commit->date;
}
buffer = read_object_with_reference(sha1, "tree", &size, NULL);
if (!buffer)
tree = parse_tree_indirect(sha1);
if (!tree)
die("not a reference to a tag, commit or tree object: %s",
sha1_to_hex(sha1));
if (!archive_time)
@ -434,8 +404,7 @@ int main(int argc, char **argv)
if (basedir)
write_header((unsigned char *)"0", TYPEFLAG_DIR, NULL, NULL,
basedir, 040777, NULL, 0);
traverse_tree(buffer, size, NULL);
free(buffer);
traverse_tree(tree, NULL);
write_trailer();
return 0;
}