Git development
 help / color / mirror / Atom feed
* Re: Git benchmark - comparison with Bazaar, Darcs, Git and Mercurial
From: Linus Torvalds @ 2007-08-01  2:14 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: Git Mailing List
In-Reply-To: <200708010216.59750.jnareb@gmail.com>



On Wed, 1 Aug 2007, Jakub Narebski wrote:
> 
> If I remember correctly there were some patches to git which tried to 
> better deal with large blobs. In this simple benchmark git was 
> outperformed by Mercurial and even Bazaar-NG a bit.

It's almost certainly not the binary blobs.

I think almost all the difference is from the cloning, without repacking 
the souce or using a local clone.

The default action for a git clone is to create a pack-file, and do a 
local clone as if you did it over the network. That is obviously much 
slower than using the "-l" flag for the _clone_ action, but it tends to be 
better for the end result - since you get a nice packed starting point, 
and none of the confusion with hardlinks etc.

[ Maybe I'm just a worry-wart, but hardlinking two repos still makes me 
  worried. Even though we never modify the object files. 

  Quite frankly, I almost wish we hadn't ever done "-l" at all, and I 
  cannot really suggest using it. Either use "-s" for the truly shared 
  repository, or use the default pack-generating one. The hardlinking one 
  was simple and made sense, but it's really not very nice.

  But that aversion to "git clone -l" is really totally illogical. The way 
  we do the object handling, hardlinking object files in git is just about 
  the most safe operation you can think of - and I *still* shudder at it ]

Now, I think the "always act as if you were network transparent" by 
default is great, but especially if you have never run "git gc" to 
generate a pack to begin with, it's going to be a very costly thing. And I 
think that's what the numbers show. That's the only op we do a *lot* worse 
on than we should.

(The "nonconflicting merge" is probably - once more - the diffstat 
generation that bites us. That's generally the most costly thing of the 
whole merge, but I *love* the diffstat).

That said, even if he had done a "git gc", to be fair he would have had to 
include the cost of that first garbage collect in the "initial import", so 
the end result would have been exactly the same. Git _does_ end up having 
a very odd performance profile, and while it's optimized for certain 
thing, the "initial import" is not one of them.

(Which admittedly is a bit odd. The reason I didn't ever seriously even 
consider monotone was that the initial import was so *incredibly* sucky, 
and took hours for the kernel. So use "-l" for benchmarks, and damn my 
"I hate hardlinking repos" idiocy).

So the only way to truly do a fast initial import *and* get a reasonably 
good initial clone is likely one of:

 - take full advantage of git, and use local branches, instead of 
   bothering with lots of clones.

   I think that this is often the right thing to do, but it's obviously 
   not fair for comparisons, since it's really something different from 
   what's likely available in the other SCM's. But it's the "git way".

 - use "git clone -s" (or "-l").

   I think the hg numbers are the result of hg defaulting to "-l" 
   behaviour.  Which makes sense for hg, since people need to clone more 
   (in git, you'd generally work with local branches instead).

 - or the initial import would be done with some "git fast-import" thing, 
   rather than "git add ." We don't do it now, and the resulting pack-file 
   wouldn't be optimal, but it would be reasonable. It would at least cut 
   down a _bit_ on the clone cost.

The other reaction I took away from that (quite reasonable, I think) 
comparison is that I think Murdock would have been much happier if git 
diff defaulted to "-C". We don't do that (for the best of reasons: 
interoperability), but maybe we should document the "-M/-C" options more. 

The options do show up in the man-page, but apparently not 
obviously enough, since he hadn't noticed.

			Linus

^ permalink raw reply

* dangling blob which is not dangling at all
From: Domenico Andreoli @ 2007-08-01  1:34 UTC (permalink / raw)
  To: git

Hi,

  first of all, I want to thank Linus and you all for git, it is
revolutionizing my every-day work flow. Exceptional.

Playing with my central bare git repository (yes, I am a former
CVS/SVN user) and trying to lose data I discovered something I am not
understanding well.

Running git fsck --no-reflogs I found some dangling objects (I have
to say I enjoyed a lot in navigating commits, trees and blobs with
plumbing... really!), two were commits and one was a blob.

One of the commits was there because I pushed (forcing) from a working
repository after a git reset HEAD^. I checked it and removed it and
all the other dependant objects until the blob which contained the new
version of that file. It seems I even understood what I was doing! ;)
Until here, everything had been smooth.

Second commit was something pushed from another repository but at the
right head was strangely recorded with a different hash. Removing it,
its tree and another sub-tree, no blob was pending. So the final blob
containing the change was still used elsewhere, indeed by the "right
head" of above. While I would expect this in a working repository where
merging is happening all the day, it is not clear how it happened to
my central repository, where nobody does any work. Any idea?

And now, what I think is a bug, the dangling blob. It is signaled as
dangling but it is not. Hunting for a commit/tree/blob to compare it to
in order to understand which modification it was hiding, I found a tree
object which referred to it, which by definition of "dangling object"
should not exist. So fsck looks f*cked... and I am well available to
understand what is going wrong here, but please help me.

$ git fsck --no-reflogs
dangling blob e5d444e61b834c34710ce8fb5cb176e20e5894e1
$ git-ls-tree 70b58535361eb633d44d4f1275af3421ca6a5ed7
...
100644 blob e5d444e61b834c34710ce8fb5cb176e20e5894e1    link_stream.c
...

If you read me until here, good night! ;)

Cheers,
Domenico

-----[ Domenico Andreoli, aka cavok
 --[ http://www.dandreoli.com/gpgkey.asc
   ---[ 3A0F 2F80 F79C 678A 8936  4FEE 0677 9033 A20E BC50

^ permalink raw reply

* Re: [PATCH] add option to find zlib in custom path
From: Robert Schiele @ 2007-08-01  1:21 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7v7iogdpw5.fsf@assigned-by-dhcp.cox.net>

[-- Attachment #1: Type: text/plain, Size: 487 bytes --]

On Tue, Jul 31, 2007 at 06:06:50PM -0700, Junio C Hamano wrote:
> Queued already, integration tested last night, been too busy to
> push it out.
> 
> Patience, Ok?

Sure, that's perferctly ok.  Take the time you need.  Just wanted to know that
there is nothing wrong in principle with the patches so that I could do more
without wasting time.

Robert

-- 
Robert Schiele
Dipl.-Wirtsch.informatiker	mailto:rschiele@gmail.com

"Quidquid latine dictum sit, altum sonatur."

[-- Attachment #2: Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply

* Re: [PATCH 0/9] work-tree clean ups
From: Johannes Schindelin @ 2007-08-01  1:13 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, matled
In-Reply-To: <7vbqdsdqfd.fsf@assigned-by-dhcp.cox.net>

Hi,

On Tue, 31 Jul 2007, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > There is not really much that can be done about step 6/9: if we are in a 
> > work tree: that does not mean that we are _not_ in the git_dir.  (And no, 
> > this does not break git-clean, as a work tree is a work tree is a work 
> > tree.  If the user was stupid enough to specify the same directory as 
> > GIT_DIR and GIT_WORK_TREE, then that is _her_ problem.  Git is a powerful 
> > tool, and you can harm yourself with it.  Tough.)
> 
> I think we might have a slight misunderstanding.  The "clean"
> issue that was raised in an ancient thread was this sequence:
> 
> 	$ git init
>         $ cd .git
>         $ git clean
> 
> It did not involve GIT_DIR (nor GIT_WORK_TREE as it was not even
> there).

I very much _did_ mean that case.  When "git clean" is run in ".git/", it 
should not say that it is in the working tree.  But I guess that my patch 
series is not really looking out for that;  I'll make that an add-on 
patch.  (But that _will_ have to wait until tomorrow afternoon.)

Ciao,
Dscho

^ permalink raw reply

* Re: [PATCH] add option to find zlib in custom path
From: Junio C Hamano @ 2007-08-01  1:06 UTC (permalink / raw)
  To: Robert Schiele; +Cc: git
In-Reply-To: <20070801010156.GF29424@schiele.dyndns.org>

Queued already, integration tested last night, been too busy to
push it out.

Patience, Ok?

^ permalink raw reply

* Re: [PATCH] add option to find zlib in custom path
From: Robert Schiele @ 2007-08-01  1:01 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano
In-Reply-To: <20070729183545.GC29424@schiele.dyndns.org>

[-- Attachment #1: Type: text/plain, Size: 1120 bytes --]

On Sun, Jul 29, 2007 at 08:35:45PM +0200, Robert Schiele wrote:
> Some systems do not provide zlib development headers and libraries in
> default search path of the compiler.  For these systems we should allow
> specifying the location by --with-zlib=PATH or by setting ZLIB_PATH in
> the makefile.

Just wanted to ping about that patch I sent.  After I did not get any response
on earlier patches I sent I found that they did not comply to the patch
submission policy.  Thus I did this one according to the policy (as far as I
understand it but still don't get any reply.

So I just wanted to ask what's the cause of that?

Didn't you just have time to look at it?

Did I do something wrong?  In that case it would be useful if I knew what it
is.

Any other reason?

I mean if it is a timing issue it is not a big issue.  Just wanted to know
whether it does make sense to submit more patches or if they all get lost for
some reason I am currently not aware of.

Robert

-- 
Robert Schiele
Dipl.-Wirtsch.informatiker	mailto:rschiele@gmail.com

"Quidquid latine dictum sit, altum sonatur."

[-- Attachment #2: Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply

* Re: [PATCH 0/9] work-tree clean ups
From: Junio C Hamano @ 2007-08-01  0:55 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git, matled
In-Reply-To: <Pine.LNX.4.64.0708010058130.14781@racer.site>

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> There is not really much that can be done about step 6/9: if we are in a 
> work tree: that does not mean that we are _not_ in the git_dir.  (And no, 
> this does not break git-clean, as a work tree is a work tree is a work 
> tree.  If the user was stupid enough to specify the same directory as 
> GIT_DIR and GIT_WORK_TREE, then that is _her_ problem.  Git is a powerful 
> tool, and you can harm yourself with it.  Tough.)

I think we might have a slight misunderstanding.  The "clean"
issue that was raised in an ancient thread was this sequence:

	$ git init
        $ cd .git
        $ git clean

It did not involve GIT_DIR (nor GIT_WORK_TREE as it was not even
there).  The point was that not all subdirectories of the
toplevel (i.e. the directory on the filesystem that corresponds
to the root level of your index entries and trees contained in
your commits) were safe to answer yes when asked "are we safe to
perform this worktree oriented command here".  Because no trees
nor index would have .git/ subdirectory tracked, "git clean"
will happily remove everything under .git/ (which is $cwd in the
above sequence).

I personally feel that the above sequence is a pilot error and
not worth worrying about, but as people wanted to have that
extra safety, and as we added that (arguably stupid) safety way
before the WORK_TREE stuff, we should mention it if we are
changing the behaviour and lifting it with this patch series.

> Note: if you are in a bare repository (a repository which either says 
> "core.bare = false" in the config, or which is a direct ancestor 
> directory, i.e. ../[...]/.. of the current working directory) there will 
> _not_ be an automatic working directory assignment.  You will be operating 
> _without_ any work tree, unless you specify one.

Sorry, I cannot interpret the condition part of the sentence,
nor "There will _not_ be an automatic assignment" part.

By the latter, do you mean to say your $cwd is assumed to be the
top of the working tree unless GIT_WORK_TREE or core.worktree,
if you are in a bare repository?  Or it is assumed that you do
not have a worktree and worktree oriented operations that
require a worktree such as "git diff-files" and "git status"
will fail?

> I somehow feel that core.bare = true weighs more than core.worktree = 
> /some/thing, and therefore I implemented it that way, but hey, if enough 
> people disagree, then I'll change it.

Personally, I think

 [core]
     bare = true
     worktree = /some/where

is a configuration error, but probably I am missing a useful use
case for such a configuration?

> IMHO we should (probably after 1.5.3) change setup_git_directory_gently() 
> to call check_repository_format() in every return path, so that we 
> ascertain that the current repository is recent enough.  Because that 
> function now checks also if the repo is bare, and if it has a worktree 
> set, in addition to ensuring a valid repository.

Agreed; gently() is there primarily because some commands do not
mind not having a git repository at all and if we do have a
repository to work against we probably should do the same checks
as setup_git_directory() would.

^ permalink raw reply

* Re: cvs2svn conversion directly to git ready for experimentation
From: Johannes Schindelin @ 2007-08-01  0:41 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: git, users
In-Reply-To: <46AFCF3E.5010805@alum.mit.edu>

Hi,

On Wed, 1 Aug 2007, Michael Haggerty wrote:

> 2. Check out the current trunk version of cvs2svn:
> 
>     svn co http://cvs2svn.tigris.org/svn/cvs2svn/trunk cvs2svn-trunk
>     cd cvs2svn-trunk
>     make check # ...optional

FWIW I tried to clone it with "git svn", and needed to prefix the url with 
"guest", i.e.

	$ git clone http://guest@cvs2svn.tigris.org/svn/cvs2svn/trunk

and it still did not work at once.  Somehow I managed to get the 
"Username" prompt, input "guest", and left the password empty.  Even then, 
only the second attempt succeeded (I guess somehow that "password" got 
stored in $HOME/.subversion/auth/...

Ciao,
Dscho

^ permalink raw reply

* [PATCH 4/4] Clean up work-tree handling
From: Johannes Schindelin @ 2007-08-01  0:30 UTC (permalink / raw)
  To: gitster, git, matled
In-Reply-To: <Pine.LNX.4.64.0708010058130.14781@racer.site>


The old version of work-tree support was an unholy mess, barely readable,
and not to the point.

For example, why do you have to provide a worktree, when it is not used?
As in "git status".  Now it works.

Another riddle was: if you can have work trees inside the git dir, why
are some programs complaining that they need a work tree?

IOW it is allowed to call

	$ git --git-dir=../ --work-tree=. bla

when you really want to.  In this case, you are both in the git directory
and in the working tree.  So, programs have to actually test for the right
thing, namely if they are inside a working tree, and not if they are
inside a git directory.

Also, GIT_DIR=../.git should behave the same as if no GIT_DIR was
specified, unless there is a repository in the current working directory.
It does now.

The logic to determine if a repository is bare, or has a work tree
(tertium non datur), is this:

--work-tree=bla overrides GIT_WORK_TREE, which overrides core.bare = true,
which overrides core.worktree, which overrides GIT_DIR/.. when GIT_DIR
ends in /.git, which overrides the directory in which .git/ was found.

In related news, a long standing bug was fixed: when in .git/bla/x.git/,
which is a bare repository, git formerly assumed ../.. to be the
appropriate git dir.  This problem was reported by Shawn Pearce to have
caused much pain, where a colleague mistakenly ran "git init" in "/" a
long time ago, and bare repositories just would not work.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 builtin-init-db.c    |   48 ++-------
 builtin-ls-files.c   |    8 +-
 builtin-rev-parse.c  |    7 ++
 cache.h              |    2 +
 environment.c        |   35 +++++--
 git-sh-setup.sh      |    3 +-
 git.c                |   11 ++-
 setup.c              |  279 +++++++++++++++++++++++---------------------------
 t/t1500-rev-parse.sh |   20 ++--
 t/t1501-worktree.sh  |   24 ++++-
 10 files changed, 216 insertions(+), 221 deletions(-)

diff --git a/builtin-init-db.c b/builtin-init-db.c
index 66ddaeb..0d9b1e0 100644
--- a/builtin-init-db.c
+++ b/builtin-init-db.c
@@ -174,36 +174,7 @@ static void copy_templates(const char *git_dir, int len, const char *template_di
 	closedir(dir);
 }
 
-/*
- * Get the full path to the working tree specified in $GIT_WORK_TREE
- * or NULL if no working tree is specified.
- */
-static const char *get_work_tree(void)
-{
-	const char *git_work_tree;
-	char cwd[PATH_MAX];
-	static char worktree[PATH_MAX];
-
-	git_work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
-	if (!git_work_tree)
-		return NULL;
-	if (!getcwd(cwd, sizeof(cwd)))
-		die("Unable to read current working directory");
-	if (chdir(git_work_tree))
-		die("Cannot change directory to specified working tree '%s'",
-			git_work_tree);
-	if (git_work_tree[0] != '/') {
-		if (!getcwd(worktree, sizeof(worktree)))
-			die("Unable to read current working directory");
-		git_work_tree = worktree;
-	}
-	if (chdir(cwd))
-		die("Cannot come back to cwd");
-	return git_work_tree;
-}
-
-static int create_default_files(const char *git_dir, const char *git_work_tree,
-	const char *template_path)
+static int create_default_files(const char *git_dir, const char *template_path)
 {
 	unsigned len = strlen(git_dir);
 	static char path[PATH_MAX];
@@ -282,16 +253,16 @@ static int create_default_files(const char *git_dir, const char *git_work_tree,
 	}
 	git_config_set("core.filemode", filemode ? "true" : "false");
 
-	if (is_bare_repository() && !git_work_tree) {
+	if (is_bare_repository())
 		git_config_set("core.bare", "true");
-	}
 	else {
+		const char *work_tree = get_git_work_tree();
 		git_config_set("core.bare", "false");
 		/* allow template config file to override the default */
 		if (log_all_ref_updates == -1)
 		    git_config_set("core.logallrefupdates", "true");
-		if (git_work_tree)
-			git_config_set("core.worktree", git_work_tree);
+		if (work_tree != git_work_tree_cfg)
+			git_config_set("core.worktree", work_tree);
 	}
 	return reinit;
 }
@@ -308,7 +279,6 @@ static const char init_db_usage[] =
 int cmd_init_db(int argc, const char **argv, const char *prefix)
 {
 	const char *git_dir;
-	const char *git_work_tree;
 	const char *sha1_dir;
 	const char *template_dir = NULL;
 	char *path;
@@ -329,7 +299,11 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
 			usage(init_db_usage);
 	}
 
-	git_work_tree = get_work_tree();
+	git_work_tree_cfg = xcalloc(PATH_MAX, 1);
+	if (!getcwd(git_work_tree_cfg, PATH_MAX))
+		die ("Cannot access current working directory.");
+	if (access(get_git_work_tree(), X_OK))
+		die ("Cannot access work tree '%s'", get_git_work_tree());
 
 	/*
 	 * Set up the default .git directory contents
@@ -346,7 +320,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
 	 */
 	check_repository_format();
 
-	reinit = create_default_files(git_dir, git_work_tree, template_dir);
+	reinit = create_default_files(git_dir, template_dir);
 
 	/*
 	 * And set up the object store.
diff --git a/builtin-ls-files.c b/builtin-ls-files.c
index 61577ea..d36181a 100644
--- a/builtin-ls-files.c
+++ b/builtin-ls-files.c
@@ -469,9 +469,11 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
 		break;
 	}
 
-	if (require_work_tree &&
-			(!is_inside_work_tree() || is_inside_git_dir()))
-		die("This operation must be run in a work tree");
+	if (require_work_tree && !is_inside_work_tree()) {
+		const char *work_tree = get_git_work_tree();
+		if (!work_tree || chdir(work_tree))
+			die("This operation must be run in a work tree");
+	}
 
 	pathspec = get_pathspec(prefix, argv + i);
 
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index 497903a..15bdb72 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -320,6 +320,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
 				continue;
 			}
 			if (!strcmp(arg, "--show-cdup")) {
+				if (!is_inside_work_tree()) {
+					const char *work_tree =
+						get_git_work_tree();
+					if (work_tree)
+						printf("%s\n", work_tree);
+					continue;
+				}
 				const char *pfx = prefix;
 				while (pfx) {
 					pfx = strchr(pfx, '/');
diff --git a/cache.h b/cache.h
index e1f94cb..e97af18 100644
--- a/cache.h
+++ b/cache.h
@@ -208,6 +208,7 @@ enum object_type {
 extern int is_bare_repository_cfg;
 extern int is_bare_repository(void);
 extern int is_inside_git_dir(void);
+extern char *git_work_tree_cfg;
 extern int is_inside_work_tree(void);
 extern const char *get_git_dir(void);
 extern char *get_object_directory(void);
@@ -215,6 +216,7 @@ extern char *get_refs_directory(void);
 extern char *get_index_file(void);
 extern char *get_graft_file(void);
 extern int set_git_dir(const char *path);
+extern const char *get_git_work_tree(void);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
diff --git a/environment.c b/environment.c
index a571fae..2af12fd 100644
--- a/environment.c
+++ b/environment.c
@@ -35,6 +35,10 @@ int pager_in_use;
 int pager_use_color = 1;
 int auto_crlf = 0;	/* 1: both ways, -1: only when adding git objects */
 
+/* This is set by setup_git_dir_gently() and/or git_default_config() */
+char *git_work_tree_cfg;
+static const char *work_tree;
+
 static const char *git_dir;
 static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;
 
@@ -62,15 +66,8 @@ static void setup_git_env(void)
 
 int is_bare_repository(void)
 {
-	const char *dir, *s;
-	if (0 <= is_bare_repository_cfg)
-		return is_bare_repository_cfg;
-
-	dir = get_git_dir();
-	if (!strcmp(dir, DEFAULT_GIT_DIR_ENVIRONMENT))
-		return 0;
-	s = strrchr(dir, '/');
-	return !s || strcmp(s + 1, DEFAULT_GIT_DIR_ENVIRONMENT);
+	/* if core.bare is not 'false', let's see if there is a work tree */
+	return is_bare_repository_cfg && !get_git_work_tree();
 }
 
 const char *get_git_dir(void)
@@ -80,6 +77,26 @@ const char *get_git_dir(void)
 	return git_dir;
 }
 
+const char *get_git_work_tree(void)
+{
+	static int initialized = 0;
+	if (!initialized) {
+		work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
+		/* core.bare = true overrides implicit and config work tree */
+		if (!work_tree && is_bare_repository_cfg < 1) {
+			work_tree = git_work_tree_cfg;
+			/* make_absolute_path also normalizes the path */
+			if (work_tree && !is_absolute_path(work_tree))
+				work_tree = xstrdup(make_absolute_path(git_path(work_tree)));
+		} else if (work_tree)
+			work_tree = xstrdup(make_absolute_path(work_tree));
+		initialized = 1;
+		if (work_tree)
+			is_bare_repository_cfg = 0;
+	}
+	return work_tree;
+}
+
 char *get_object_directory(void)
 {
 	if (!git_object_dir)
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index c51985e..7bef43f 100755
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -59,8 +59,7 @@ cd_to_toplevel () {
 }
 
 require_work_tree () {
-	test $(git rev-parse --is-inside-work-tree) = true &&
-	test $(git rev-parse --is-inside-git-dir) = false ||
+	test $(git rev-parse --is-inside-work-tree) = true ||
 	die "fatal: $0 cannot be used without a working tree."
 }
 
diff --git a/git.c b/git.c
index a647f9c..2433355 100644
--- a/git.c
+++ b/git.c
@@ -272,9 +272,14 @@ static int run_command(struct cmd_struct *p, int argc, const char **argv)
 		prefix = setup_git_directory();
 	if (p->option & USE_PAGER)
 		setup_pager();
-	if ((p->option & NEED_WORK_TREE) &&
-	    (!is_inside_work_tree() || is_inside_git_dir()))
-		die("%s must be run in a work tree", p->cmd);
+	if (p->option & NEED_WORK_TREE) {
+		const char *work_tree = get_git_work_tree();
+		const char *git_dir = get_git_dir();
+		if (!is_absolute_path(git_dir))
+			set_git_dir(make_absolute_path(git_dir));
+		if (!work_tree || chdir(work_tree))
+			die("%s must be run in a work tree", p->cmd);
+	}
 	trace_argv_printf(argv, argc, "trace: built-in: git");
 
 	status = p->fn(argc, argv, prefix);
diff --git a/setup.c b/setup.c
index b54d65f..8237fe3 100644
--- a/setup.c
+++ b/setup.c
@@ -1,4 +1,8 @@
 #include "cache.h"
+#include "dir.h"
+
+static int inside_git_dir = -1;
+static int inside_work_tree = -1;
 
 const char *prefix_path(const char *prefix, int len, const char *path)
 {
@@ -170,100 +174,89 @@ static int is_git_directory(const char *suspect)
 	return 1;
 }
 
-static int inside_git_dir = -1;
-
 int is_inside_git_dir(void)
 {
-	if (inside_git_dir >= 0)
-		return inside_git_dir;
-	die("BUG: is_inside_git_dir called before setup_git_directory");
+	if (inside_git_dir < 0)
+		inside_git_dir = is_inside_dir(get_git_dir());
+	return inside_git_dir;
 }
 
-static int inside_work_tree = -1;
-
 int is_inside_work_tree(void)
 {
-	if (inside_git_dir >= 0)
-		return inside_work_tree;
-	die("BUG: is_inside_work_tree called before setup_git_directory");
+	if (inside_work_tree < 0)
+		inside_work_tree = is_inside_dir(get_git_work_tree());
+	return inside_work_tree;
 }
 
-static char *gitworktree_config;
-
-static int git_setup_config(const char *var, const char *value)
+/*
+ * If no worktree was given, and we are outside of a default work tree,
+ * now is the time to set it.
+ *
+ * In other words, if the user calls git with something like
+ *
+ *	git --git-dir=/some/where/else/.git bla
+ *
+ * default to /some/where/else as working directory; if the specified
+ * git-dir does not end in "/.git", the cwd is used as working directory.
+ */
+const char* set_work_tree(const char *dir)
 {
-	if (!strcmp(var, "core.worktree")) {
-		if (gitworktree_config)
-			strlcpy(gitworktree_config, value, PATH_MAX);
-		return 0;
+	char dir_buffer[PATH_MAX];
+	static char buffer[PATH_MAX + 1], *rel = NULL;
+	int len, postfix_len = strlen(DEFAULT_GIT_DIR_ENVIRONMENT) + 1;
+
+	/* strip the variable 'dir' of the postfix "/.git" if it has it */
+	len = strlen(dir);
+	if (len > postfix_len && !strcmp(dir + len - postfix_len,
+				"/" DEFAULT_GIT_DIR_ENVIRONMENT)) {
+			strncpy(dir_buffer, dir, len - postfix_len);
+
+		/* are we inside the default work tree? */
+		rel = get_relative_cwd(buffer, sizeof(buffer), dir_buffer);
+	}
+	/* if rel is set, the cwd is _not_ the current working tree */
+	if (rel && *rel) {
+		if (!is_absolute_path(dir))
+			set_git_dir(make_absolute_path(dir));
+		dir = dir_buffer;
+		chdir(dir);
+		strcat(rel, "/");
+		inside_git_dir = 0;
+	} else {
+		rel = NULL;
+		dir = getcwd(buffer, sizeof(buffer));
 	}
-	return git_default_config(var, value);
+	git_work_tree_cfg = xstrdup(dir);
+	inside_work_tree = 1;
+
+	return rel;
 }
 
+/*
+ * We cannot decide in this function whether we are in the work tree or
+ * not, since the config can only be read _after_ this function was called.
+ */
 const char *setup_git_directory_gently(int *nongit_ok)
 {
+	const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
 	static char cwd[PATH_MAX+1];
-	char worktree[PATH_MAX+1], gitdir[PATH_MAX+1];
-	const char *gitdirenv, *gitworktree;
-	int wt_rel_gitdir = 0;
+	const char *gitdirenv;
+	int len, offset;
 
+	/*
+	 * If GIT_DIR is set explicitly, we're not going
+	 * to do any discovery, but we still do repository
+	 * validation.
+	 */
 	gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
-	if (!gitdirenv) {
-		int len, offset;
-
-		if (!getcwd(cwd, sizeof(cwd)-1))
-			die("Unable to read current working directory");
-
-		offset = len = strlen(cwd);
-		for (;;) {
-			if (is_git_directory(".git"))
-				break;
-			if (offset == 0) {
-				offset = -1;
-				break;
-			}
-			chdir("..");
-			while (cwd[--offset] != '/')
-				; /* do nothing */
-		}
-
-		if (offset >= 0) {
-			inside_work_tree = 1;
-			git_config(git_default_config);
-			if (offset == len) {
-				inside_git_dir = 0;
-				return NULL;
-			}
-
-			cwd[len++] = '/';
-			cwd[len] = '\0';
-			inside_git_dir = !prefixcmp(cwd + offset + 1, ".git/");
-			return cwd + offset + 1;
-		}
-
-		if (chdir(cwd))
-			die("Cannot come back to cwd");
-		if (!is_git_directory(".")) {
-			if (nongit_ok) {
-				*nongit_ok = 1;
-				return NULL;
-			}
-			die("Not a git repository");
-		}
-		setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
-		gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
-		if (!gitdirenv)
-			die("getenv after setenv failed");
-	}
-
-	if (PATH_MAX - 40 < strlen(gitdirenv)) {
-		if (nongit_ok) {
-			*nongit_ok = 1;
+	if (gitdirenv) {
+		if (PATH_MAX - 40 < strlen(gitdirenv))
+			die("'$%s' too big", GIT_DIR_ENVIRONMENT);
+		if (is_git_directory(gitdirenv)) {
+			if (!work_tree_env)
+				return set_work_tree(gitdirenv);
 			return NULL;
 		}
-		die("$%s too big", GIT_DIR_ENVIRONMENT);
-	}
-	if (!is_git_directory(gitdirenv)) {
 		if (nongit_ok) {
 			*nongit_ok = 1;
 			return NULL;
@@ -273,92 +266,53 @@ const char *setup_git_directory_gently(int *nongit_ok)
 
 	if (!getcwd(cwd, sizeof(cwd)-1))
 		die("Unable to read current working directory");
-	if (chdir(gitdirenv)) {
-		if (nongit_ok) {
-			*nongit_ok = 1;
-			return NULL;
-		}
-		die("Cannot change directory to $%s '%s'",
-			GIT_DIR_ENVIRONMENT, gitdirenv);
-	}
-	if (!getcwd(gitdir, sizeof(gitdir)-1))
-		die("Unable to read current working directory");
-	if (chdir(cwd))
-		die("Cannot come back to cwd");
 
 	/*
-	 * In case there is a work tree we may change the directory,
-	 * therefore make GIT_DIR an absolute path.
+	 * Test in the following order (relative to the cwd):
+	 * - .git/
+	 * - ./ (bare)
+	 * - ../.git/
+	 * - ../ (bare)
+	 * - ../../.git/
+	 *   etc.
 	 */
-	if (gitdirenv[0] != '/') {
-		setenv(GIT_DIR_ENVIRONMENT, gitdir, 1);
-		gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
-		if (!gitdirenv)
-			die("getenv after setenv failed");
-		if (PATH_MAX - 40 < strlen(gitdirenv)) {
-			if (nongit_ok) {
-				*nongit_ok = 1;
-				return NULL;
-			}
-			die("$%s too big after expansion to absolute path",
-				GIT_DIR_ENVIRONMENT);
-		}
-	}
-
-	strcat(cwd, "/");
-	strcat(gitdir, "/");
-	inside_git_dir = !prefixcmp(cwd, gitdir);
-
-	gitworktree = getenv(GIT_WORK_TREE_ENVIRONMENT);
-	if (!gitworktree) {
-		gitworktree_config = worktree;
-		worktree[0] = '\0';
-	}
-	git_config(git_setup_config);
-	if (!gitworktree) {
-		gitworktree_config = NULL;
-		if (worktree[0])
-			gitworktree = worktree;
-		if (gitworktree && gitworktree[0] != '/')
-			wt_rel_gitdir = 1;
-	}
-
-	if (wt_rel_gitdir && chdir(gitdirenv))
-		die("Cannot change directory to $%s '%s'",
-			GIT_DIR_ENVIRONMENT, gitdirenv);
-	if (gitworktree && chdir(gitworktree)) {
-		if (nongit_ok) {
-			if (wt_rel_gitdir && chdir(cwd))
-				die("Cannot come back to cwd");
-			*nongit_ok = 1;
+	offset = len = strlen(cwd);
+	for (;;) {
+		if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
+			break;
+		if (is_git_directory(".")) {
+			inside_git_dir = 1;
+			if (!work_tree_env)
+				inside_work_tree = 0;
+			setenv(GIT_DIR_ENVIRONMENT, ".", 1);
 			return NULL;
 		}
-		if (wt_rel_gitdir)
-			die("Cannot change directory to working tree '%s'"
-				" from $%s", gitworktree, GIT_DIR_ENVIRONMENT);
-		else
-			die("Cannot change directory to working tree '%s'",
-				gitworktree);
-	}
-	if (!getcwd(worktree, sizeof(worktree)-1))
-		die("Unable to read current working directory");
-	strcat(worktree, "/");
-	inside_work_tree = !prefixcmp(cwd, worktree);
-
-	if (gitworktree && inside_work_tree && !prefixcmp(worktree, gitdir) &&
-	    strcmp(worktree, gitdir)) {
-		inside_git_dir = 0;
+		chdir("..");
+		do {
+			if (!offset) {
+				if (nongit_ok) {
+					if (chdir(cwd))
+						die("Cannot come back to cwd");
+					*nongit_ok = 1;
+					return NULL;
+				}
+				die("Not a git repository");
+			}
+		} while (cwd[--offset] != '/');
 	}
 
-	if (!inside_work_tree) {
-		if (chdir(cwd))
-			die("Cannot come back to cwd");
+	inside_git_dir = 0;
+	if (!work_tree_env)
+		inside_work_tree = 1;
+	git_work_tree_cfg = xstrndup(cwd, offset);
+	if (offset == len)
 		return NULL;
-	}
 
-	if (!strcmp(cwd, worktree))
-		return NULL;
-	return cwd+strlen(worktree);
+	/* Make "offset" point to past the '/', and add a '/' at the end */
+	offset++;
+	cwd[len++] = '/';
+	cwd[len] = 0;
+	return cwd + offset;
 }
 
 int git_config_perm(const char *var, const char *value)
@@ -386,6 +340,16 @@ int check_repository_format_version(const char *var, const char *value)
 		repository_format_version = git_config_int(var, value);
 	else if (strcmp(var, "core.sharedrepository") == 0)
 		shared_repository = git_config_perm(var, value);
+	else if (strcmp(var, "core.bare") == 0) {
+		is_bare_repository_cfg = git_config_bool(var, value);
+		if (is_bare_repository_cfg == 1)
+			inside_work_tree = -1;
+	} else if (strcmp(var, "core.worktree") == 0) {
+		if (git_work_tree_cfg)
+			free(git_work_tree_cfg);
+		git_work_tree_cfg = xstrdup(value);
+		inside_work_tree = -1;
+	}
 	return 0;
 }
 
@@ -402,5 +366,16 @@ const char *setup_git_directory(void)
 {
 	const char *retval = setup_git_directory_gently(NULL);
 	check_repository_format();
+
+	/* If the work tree is not the default one, recompute prefix */
+	if (inside_work_tree < 0) {
+		static char buffer[PATH_MAX + 1];
+		if (retval && chdir(retval))
+			die ("Could not jump back into original cwd");
+		char *rel = get_relative_cwd(buffer, PATH_MAX,
+				get_git_work_tree());
+		return rel && *rel ? strcat(rel, "/") : NULL;
+	}
+
 	return retval;
 }
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
index ec49966..bea40cb 100755
--- a/t/t1500-rev-parse.sh
+++ b/t/t1500-rev-parse.sh
@@ -31,9 +31,9 @@ test_rev_parse() {
 test_rev_parse toplevel false false true ''
 
 cd .git || exit 1
-test_rev_parse .git/ false true true .git/
+test_rev_parse .git/ true true false ''
 cd objects || exit 1
-test_rev_parse .git/objects/ false true true .git/objects/
+test_rev_parse .git/objects/ true true false ''
 cd ../.. || exit 1
 
 mkdir -p sub/dir || exit 1
@@ -42,7 +42,7 @@ test_rev_parse subdirectory false false true sub/dir/
 cd ../.. || exit 1
 
 git config core.bare true
-test_rev_parse 'core.bare = true' true false true
+test_rev_parse 'core.bare = true' true false false
 
 git config --unset core.bare
 test_rev_parse 'core.bare undefined' false false true
@@ -50,28 +50,28 @@ test_rev_parse 'core.bare undefined' false false true
 mkdir work || exit 1
 cd work || exit 1
 export GIT_DIR=../.git
-export GIT_CONFIG="$GIT_DIR"/config
+export GIT_CONFIG="$(pwd)"/../.git/config
 
 git config core.bare false
-test_rev_parse 'GIT_DIR=../.git, core.bare = false' false false true ''
+test_rev_parse 'GIT_DIR=../.git, core.bare = false' false false true work/
 
 git config core.bare true
-test_rev_parse 'GIT_DIR=../.git, core.bare = true' true false true ''
+test_rev_parse 'GIT_DIR=../.git, core.bare = true' true false false ''
 
 git config --unset core.bare
-test_rev_parse 'GIT_DIR=../.git, core.bare undefined' false false true ''
+test_rev_parse 'GIT_DIR=../.git, core.bare undefined' false false true work/
 
 mv ../.git ../repo.git || exit 1
 export GIT_DIR=../repo.git
-export GIT_CONFIG="$GIT_DIR"/config
+export GIT_CONFIG="$(pwd)"/../repo.git/config
 
 git config core.bare false
 test_rev_parse 'GIT_DIR=../repo.git, core.bare = false' false false true ''
 
 git config core.bare true
-test_rev_parse 'GIT_DIR=../repo.git, core.bare = true' true false true ''
+test_rev_parse 'GIT_DIR=../repo.git, core.bare = true' true false false ''
 
 git config --unset core.bare
-test_rev_parse 'GIT_DIR=../repo.git, core.bare undefined' true false true ''
+test_rev_parse 'GIT_DIR=../repo.git, core.bare undefined' false false true ''
 
 test_done
diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
index aadeeab..7322161 100755
--- a/t/t1501-worktree.sh
+++ b/t/t1501-worktree.sh
@@ -33,17 +33,17 @@ mv .git repo.git || exit 1
 
 say "core.worktree = relative path"
 export GIT_DIR=repo.git
-export GIT_CONFIG=$GIT_DIR/config
+export GIT_CONFIG="$(pwd)"/$GIT_DIR/config
 unset GIT_WORK_TREE
 git config core.worktree ../work
 test_rev_parse 'outside'      false false false
 cd work || exit 1
 export GIT_DIR=../repo.git
-export GIT_CONFIG=$GIT_DIR/config
+export GIT_CONFIG="$(pwd)"/$GIT_DIR/config
 test_rev_parse 'inside'       false false true ''
 cd sub/dir || exit 1
 export GIT_DIR=../../../repo.git
-export GIT_CONFIG=$GIT_DIR/config
+export GIT_CONFIG="$(pwd)"/$GIT_DIR/config
 test_rev_parse 'subdirectory' false false true sub/dir/
 cd ../../.. || exit 1
 
@@ -84,9 +84,23 @@ test_rev_parse 'in repo.git'              false true  false
 cd objects || exit 1
 test_rev_parse 'in repo.git/objects'      false true  false
 cd ../work || exit 1
-test_rev_parse 'in repo.git/work'         false false true ''
+test_rev_parse 'in repo.git/work'         false true true ''
 cd sub/dir || exit 1
-test_rev_parse 'in repo.git/sub/dir' false false true sub/dir/
+test_rev_parse 'in repo.git/sub/dir' false true true sub/dir/
 cd ../../../.. || exit 1
 
+test_expect_success 'repo finds its work tree' '
+	(cd repo.git &&
+	 : > work/sub/dir/untracked &&
+	 test sub/dir/untracked = "$(git ls-files --others)")
+'
+
+test_expect_success 'repo finds its work tree from work tree, too' '
+	(cd repo.git/work/sub/dir &&
+	 : > tracked &&
+	 git --git-dir=../../.. add tracked &&
+	 cd ../../.. &&
+	 test sub/dir/tracked = "$(git ls-files)")
+'
+
 test_done
-- 
1.5.3.rc3.94.g3024

^ permalink raw reply related

* [PATCH 3/4] Add set_git_dir() function
From: Johannes Schindelin @ 2007-08-01  0:29 UTC (permalink / raw)
  To: gitster, git, matled
In-Reply-To: <Pine.LNX.4.64.0708010058130.14781@racer.site>


With the function set_git_dir() you can reset the path that will
be used for git_path(), git_dir() and friends.

The responsibility to close files and throw away information from the
old git_dir lies with the caller.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 cache.h       |    1 +
 environment.c |    8 ++++++++
 2 files changed, 9 insertions(+), 0 deletions(-)

diff --git a/cache.h b/cache.h
index 98af530..e1f94cb 100644
--- a/cache.h
+++ b/cache.h
@@ -214,6 +214,7 @@ extern char *get_object_directory(void);
 extern char *get_refs_directory(void);
 extern char *get_index_file(void);
 extern char *get_graft_file(void);
+extern int set_git_dir(const char *path);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
diff --git a/environment.c b/environment.c
index f83fb9e..a571fae 100644
--- a/environment.c
+++ b/environment.c
@@ -107,3 +107,11 @@ char *get_graft_file(void)
 		setup_git_env();
 	return git_graft_file;
 }
+
+int set_git_dir(const char *path)
+{
+	if (setenv(GIT_DIR_ENVIRONMENT, path, 1))
+		return error("Could not set GIT_DIR to '%s'", path);
+	setup_git_env();
+	return 0;
+}
-- 
1.5.3.rc3.94.g3024

^ permalink raw reply related

* [PATCH 2/4] Add functions get_relative_cwd() and is_inside_dir()
From: Johannes Schindelin @ 2007-08-01  0:29 UTC (permalink / raw)
  To: gitster, git, matled
In-Reply-To: <Pine.LNX.4.64.0708010058130.14781@racer.site>


The function get_relative_cwd() works just as getcwd(), only that it
takes an absolute path as additional parameter, returning the prefix
of the current working directory relative to the given path.  If the
cwd is no subdirectory of the given path, it returns NULL.

is_inside_dir() is just a trivial wrapper over get_relative_cwd().

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 dir.c |   32 ++++++++++++++++++++++++++++++++
 dir.h |    3 +++
 2 files changed, 35 insertions(+), 0 deletions(-)

diff --git a/dir.c b/dir.c
index 8d8faf5..5ea269a 100644
--- a/dir.c
+++ b/dir.c
@@ -642,3 +642,35 @@ file_exists(const char *f)
   struct stat sb;
   return stat(f, &sb) == 0;
 }
+
+/*
+ * get_relative_cwd() gets the prefix of the current working directory
+ * relative to 'dir'.  If we are not inside 'dir', it returns NULL.
+ * As a convenience, it also returns NULL if 'dir' is already NULL.
+ */
+char *get_relative_cwd(char *buffer, int size, const char *dir)
+{
+	char *cwd = buffer;
+
+	if (!dir || !getcwd(buffer, size))
+		return NULL;
+
+	if (!is_absolute_path(dir))
+		dir = make_absolute_path(dir);
+
+	while (*dir && *dir == *cwd) {
+		dir++;
+		cwd++;
+	}
+	if (*dir)
+		return NULL;
+	if (*cwd == '/')
+		return cwd + 1;
+	return cwd;
+}
+
+int is_inside_dir(const char *dir)
+{
+	char buffer[PATH_MAX];
+	return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL;
+}
diff --git a/dir.h b/dir.h
index ec0e8ab..f55a87b 100644
--- a/dir.h
+++ b/dir.h
@@ -61,4 +61,7 @@ extern void add_exclude(const char *string, const char *base,
 extern int file_exists(const char *);
 extern struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len);
 
+extern char *get_relative_cwd(char *buffer, int size, const char *dir);
+extern int is_inside_dir(const char *dir);
+
 #endif
-- 
1.5.3.rc3.94.g3024

#

^ permalink raw reply related

* [PATCH 1/4] Add is_absolute_path() and make_absolute_path()
From: Johannes Schindelin @ 2007-08-01  0:28 UTC (permalink / raw)
  To: gitster, git, matled
In-Reply-To: <Pine.LNX.4.64.0708010058130.14781@racer.site>


This patch adds convenience functions to work with absolute paths.
The function is_absolute_path() should help the efforts to integrate
the MinGW fork.

Note that make_absolute_path() returns a pointer to a static buffer.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 Makefile             |    2 +-
 cache.h              |    5 ++++
 path.c               |   65 ++++++++++++++++++++++++++++++++++++++++++++++++++
 t/t0000-basic.sh     |   16 ++++++++++++
 test-absolute-path.c |   11 ++++++++
 5 files changed, 98 insertions(+), 1 deletions(-)
 create mode 100644 test-absolute-path.c

diff --git a/Makefile b/Makefile
index 149df1b..41c4de0 100644
--- a/Makefile
+++ b/Makefile
@@ -937,7 +937,7 @@ endif
 
 ### Testing rules
 
-TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X
+TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-absolute-path$X
 
 all:: $(TEST_PROGRAMS)
 
diff --git a/cache.h b/cache.h
index 53801b8..98af530 100644
--- a/cache.h
+++ b/cache.h
@@ -358,6 +358,11 @@ int git_config_perm(const char *var, const char *value);
 int adjust_shared_perm(const char *path);
 int safe_create_leading_directories(char *path);
 char *enter_repo(char *path, int strict);
+static inline int is_absolute_path(const char *path)
+{
+	return path[0] == '/';
+}
+const char *make_absolute_path(const char *path);
 
 /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
 extern int sha1_object_info(const unsigned char *, unsigned long *);
diff --git a/path.c b/path.c
index dfff41f..27b4ac9 100644
--- a/path.c
+++ b/path.c
@@ -288,3 +288,68 @@ int adjust_shared_perm(const char *path)
 		return -2;
 	return 0;
 }
+
+/* We allow "recursive" symbolic links. Only within reason, though. */
+#define MAXDEPTH 5
+
+const char *make_absolute_path(const char *path)
+{
+	static char bufs[2][PATH_MAX + 1], *buf = bufs[0], *next_buf = bufs[1];
+	char cwd[1024] = "";
+	int buf_index = 1, len;
+
+	int depth = MAXDEPTH;
+	char *last_elem = NULL;
+	struct stat st;
+
+	if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
+		die ("Too long path: %.*s", 60, path);
+
+	while (depth--) {
+		if (stat(buf, &st) || !S_ISDIR(st.st_mode)) {
+			char *last_slash = strrchr(buf, '/');
+			if (last_slash) {
+				*last_slash = '\0';
+				last_elem = xstrdup(last_slash + 1);
+			} else
+				last_elem = xstrdup(buf);
+		}
+
+		if (*buf) {
+			if (!*cwd && !getcwd(cwd, sizeof(cwd)))
+				die ("Could not get current working directory");
+
+			if (chdir(buf))
+				die ("Could not switch to '%s'", buf);
+		}
+		if (getcwd(buf, PATH_MAX) < 0)
+			die ("Could not get current working directory");
+
+		if (last_elem) {
+			int len = strlen(buf);
+			if (len + strlen(last_elem) + 2 > PATH_MAX)
+				die ("Too long path name: '%s/%s'",
+						buf, last_elem);
+			buf[len] = '/';
+			strcpy(buf + len + 1, last_elem);
+			free(last_elem);
+			last_elem = NULL;
+		}
+
+		if (!lstat(buf, &st) && S_ISLNK(st.st_mode)) {
+			len = readlink(buf, next_buf, PATH_MAX);
+			if (len < 0)
+				die ("Invalid symlink: %s", buf);
+			next_buf[len] = '\0';
+			buf = next_buf;
+			buf_index = 1 - buf_index;
+			next_buf = bufs[buf_index];
+		} else
+			break;
+	}
+
+	if (*cwd && chdir(cwd))
+		die ("Could not change back to '%s'", cwd);
+
+	return buf;
+}
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index 4bba9c0..4e49d59 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -281,4 +281,20 @@ test_expect_success 'update-index D/F conflict' '
 	test $numpath0 = 1
 '
 
+test_expect_success 'absolute path works as expected' '
+	mkdir first &&
+	ln -s ../.git first/.git &&
+	mkdir second &&
+	ln -s ../first second/other &&
+	mkdir third &&
+	dir="$(cd .git; pwd -P)" &&
+	dir2=third/../second/other/.git &&
+	test "$dir" = "$(test-absolute-path $dir2)" &&
+	file="$dir"/index &&
+	test "$file" = "$(test-absolute-path $dir2/index)" &&
+	ln -s ../first/file .git/syml &&
+	sym="$(cd first; pwd -P)"/file &&
+	test "$sym" = "$(test-absolute-path $dir2/syml)"
+'
+
 test_done
diff --git a/test-absolute-path.c b/test-absolute-path.c
new file mode 100644
index 0000000..c959ea2
--- /dev/null
+++ b/test-absolute-path.c
@@ -0,0 +1,11 @@
+#include "cache.h"
+
+int main(int argc, char **argv)
+{
+	while (argc > 1) {
+		puts(make_absolute_path(argv[1]));
+		argc--;
+		argv++;
+	}
+	return 0;
+}
-- 
1.5.3.rc3.94.g3024

^ permalink raw reply related

* Re: [PATCH 0/9] work-tree clean ups
From: Johannes Schindelin @ 2007-08-01  0:28 UTC (permalink / raw)
  To: gitster, git, matled
In-Reply-To: <Pine.LNX.4.64.0707300016470.14781@racer.site>

Hi,

so this is yet another revision of the work-tree clean ups (sorry to all 
those who grow tired of it; I feel with you: I am tired of it, too).

Junio rightfully pointed out that the tests do not succeed after each 
single step of the series.

Alas, after thinking about it for quite some time, I do not think there is 
any way around squashing all the earlier steps 4,6--9 into one step.

There is not really much that can be done about step 6/9: if we are in a 
work tree: that does not mean that we are _not_ in the git_dir.  (And no, 
this does not break git-clean, as a work tree is a work tree is a work 
tree.  If the user was stupid enough to specify the same directory as 
GIT_DIR and GIT_WORK_TREE, then that is _her_ problem.  Git is a powerful 
tool, and you can harm yourself with it.  Tough.)

Patch 7/9 is needed, because the old logic in git-init was wrong, and only 
hidden by the fact that the work-tree logic was implemented wrongly to 
begin with.

Patches 8 and 9/9 only updated the tests to ensure a sane logic, instead 
of an unsane one.  So they are needed, too.

Note: if you are in a bare repository (a repository which either says 
"core.bare = false" in the config, or which is a direct ancestor 
directory, i.e. ../[...]/.. of the current working directory) there will 
_not_ be an automatic working directory assignment.  You will be operating 
_without_ any work tree, unless you specify one.

I somehow feel that core.bare = true weighs more than core.worktree = 
/some/thing, and therefore I implemented it that way, but hey, if enough 
people disagree, then I'll change it.

Maybe I should add two comments?

Namely that setup_git_directory_gently() does _not_ check the config if 
the repository version is right, and where the working directory is, and 
if the repository is bare.  setup_git_directory() does...

And that setup_git_directory_gently() _does_ try to be smart about 
get_git_work_tree(), is_inside_git_dir() and is_inside_work_tree() by 
assigning their return values, and only if core.bare or core.worktree (or 
--work-tree=<something> or GIT_WORK_TREE) are set, get_git_work_tree() and 
is_inside_work_tree() are reset to recalculate what is happening...  
(actually, that is not completely true: if we _know_ that GIT_WORK_TREE is 
set, or --work-tree=<something> which is almost the same, we will defer 
the calculation until one of the functions get_git_work_tree() and 
is_inside_work_tree() is called.)

IMHO we should (probably after 1.5.3) change setup_git_directory_gently() 
to call check_repository_format() in every return path, so that we 
ascertain that the current repository is recent enough.  Because that 
function now checks also if the repo is bare, and if it has a worktree 
set, in addition to ensuring a valid repository.

In hindsight, I should have separated the "check .git/, then ./, and if no 
git_dir was found, continue with the parent directory" into a separate 
patch, but frankly, I am sick and tired of the work-tree series.  It was 
not done right in the first place, and it used hard-to-understand code to 
hide the fact.

Ciao,
Dscho

P.S.: After reading my patch to the tests, I have to disagree strongly 
with my notion that _not_ cleaning up the tests to use some sane syntax 
would make them clearer.  Nevertheless, I think I'll let them stand as an 
example how _not_ to write tests.

^ permalink raw reply

* Re: Git clone error
From: Junio C Hamano @ 2007-08-01  0:22 UTC (permalink / raw)
  To: Denis Bueno; +Cc: Git Mailing List
In-Reply-To: <C2D525CB.2A81%denbuen@sandia.gov>

"Denis Bueno" <denbuen@sandia.gov> writes:

> ...  The error I see is:
>
>     git[14] > git clone /Volumes/work/scripts/
>     Initialized empty Git repository in /tmp/git/scripts/.git/
>     remote: Generating pack...
>     Done counting 80 objects.
>     remote: error: unable to unpack b28b949a1a3c8eb37ca6eefd024508fa8b253429
> header
>     fatal: unable to get type of object b28b949a1a3c8eb37ca6error:
> git-upload-pack: git-pack-objects died with error.
>     fatal: git-upload-pack: aborting due to possible repository corruption
> on the remote side.
>     remote: aborting due to possible repository corruption on the remote
> side.

First thing to check would be...

>     fatal: early EOF
>     fatal: index-pack died with error code 128
>     fetch-pack from '/Volumes/work/scripts/.git' failed.

        $ cd /Volumes/work/scripts
        $ git fsck --full

^ permalink raw reply

* Git benchmark - comparison with Bazaar, Darcs, Git and Mercurial
From: Jakub Narebski @ 2007-08-01  0:16 UTC (permalink / raw)
  To: git

I have lately added new Git speed benchmark, from Bryan Murdock blog. 
The repository is bit untypical:

<quote>  
  By performance, I mean that I used the UNIX time command to see how
  long various basic operations took. Performing the various basic
  operations gave me some insight into the usability of each as well.
  For this test I used a directory with 266 MB of files, 258 KB of which
  were text files, with the rest being image files. I know, kind of
  weird to version all those binary files, but that was the project I
  was interested in testing this out on. Your mileage may vary and all
  that. Here’s a table summarizing the real times reported by time(1):
</quote>

If I remember correctly there were some patches to git which tried to 
better deal with large blobs. In this simple benchmark git was 
outperformed by Mercurial and even Bazaar-NG a bit.

http://git.or.cz/gitwiki/GitBenchmarks#head-5657b8361895b5a02c0de39337c410e4d8dcdbce
http://bryan-murdock.blogspot.com/2007/03/cutting-edge-revision-control.html
-- 
Jakub Narebski
Poland

^ permalink raw reply

* cvs2svn conversion directly to git ready for experimentation
From: Michael Haggerty @ 2007-08-01  0:09 UTC (permalink / raw)
  To: git; +Cc: users

I am the maintainer of cvs2svn[1], which is a program for one-time
conversions from CVS to Subversion.  cvs2svn is very robust against the
many peculiarities of CVS and can convert just about every CVS
repository we have ever seen.

I've been working on a cvs2svn output pass that writes the converted CVS
repository directly into git rather than Subversion.  The code runs now
with at least one repository from our test suite of nasty CVS repositories.

Unfortunately, I am a complete git newbie, so I would very much
appreciate help from the git community with feedback and checking
whether the conversion output is reasonable and gitlike.

The git output is very preliminary and virtually untested, and has the
following limitations (hopefully to be removed in the near future):

- It is rather slow.  Among other things, it still uses RCS or CVS to
extract the contents of the CVS revisions, which will soon be changed to
win a factor of 2 or so.

- CVS allows a branch to be created from arbitrary combinations of
source revisions and/or source branches.  cvs2svn tries to create a
branch from a single source, but if it can't figure out how to, it
creates the branch using "merge" from multiple sources.  In pathological
situations, the number of merge sources for a branch can be arbitrarily
large.

- It is not very intelligent about creating tags.  When asked to create
a tag, it unconditionally creates a "tag fixup branch"[2] with the same
name and contents as the tag, then tags this branch.  The tag fixup
branch is never deleted.

- There are no checks that CVS branch and tag names are legal git names,
or indeed that any other similar limitations of git are honored.

- The data that should be fed to git-fast-input is written to two files,
which have to be loaded into git-fast-import manually.  Eventually I
will add an option to invoke git-fast-import automatically and pipe the
output directly into git-fast-import.

- Only single projects can be converted at a time.  I don't think that
this will be a significant limitation when outputting to git.


To try it out:

1. Install svn (to be able to check out cvs2svn) and either cvs or rcs.

2. Check out the current trunk version of cvs2svn:

    svn co http://cvs2svn.tigris.org/svn/cvs2svn/trunk cvs2svn-trunk
    cd cvs2svn-trunk
    make check # ...optional

3. Configure cvs2svn for your conversion.  This has to be done via the
"options-file method"[3].  See cvs2svn-example.options and
test-data/main-cvsrepos/cvs2svn-git.options as examples; the former file
includes voluminous documentation.

4. Run cvs2svn.  This outputs two git-fast-import files, with the names
specified by your options file.  In the example, these files are named
'cvs2svn-tmp/git-blob.dat' and 'cvs2svn-tmp/git-dump.dat'.

5. Initialize a git repository, and load the dump files using
git-fast-import:

    git-init
    cat cvs2svn-tmp/git-blob.dat | \
        git-fast-import --export-marks=cvs2svn-tmp/git-marks.dat
    cat cvs2svn-tmp/git-dump.dat | \
        git-fast-import --import-marks=cvs2svn-tmp/git-marks.dat


I am looking forward to your feedback.  Even better would be if somebody
wants to join forces on this project.  I would be happy to supply the
cvs2svn knowledge if you can bring the git experience.

Michael


[1] http://cvs2svn.tigris.org/
[2] http://www.kernel.org/pub/software/scm/git/docs/git-fast-import.html
[3] http://cvs2svn.tigris.org/cvs2svn.html#cmd-vs-options

^ permalink raw reply

* Git clone error
From: Denis Bueno @ 2007-07-31 23:45 UTC (permalink / raw)
  To: Git Mailing List

Hi all,

I'm a new git user (actually a darcs convert) and am running into a weird
problem on a small and simple repository.  The error I see is:

    git[14] > git clone /Volumes/work/scripts/
    Initialized empty Git repository in /tmp/git/scripts/.git/
    remote: Generating pack...
    Done counting 80 objects.
    remote: error: unable to unpack b28b949a1a3c8eb37ca6eefd024508fa8b253429
header
    fatal: unable to get type of object b28b949a1a3c8eb37ca6error:
git-upload-pack: git-pack-objects died with error.
    fatal: git-upload-pack: aborting due to possible repository corruption
on the remote side.
    remote: aborting due to possible repository corruption on the remote
side.
    fatal: early EOF
    fatal: index-pack died with error code 128
    fetch-pack from '/Volumes/work/scripts/.git' failed.

I have many git repos on my system (one for configuration files, one for
scripts, various school projects, etc.) and this is the only one that has
been problematic thus far.  As you can see I am trying to clone a local repo
-- there should be no network problems here.  As I said, the repo is small:

    scripts[19] > find . -type f | wc -l
    118

(which of course includes all of git's files.)

$ git --version
git version 1.5.2.4

$ uname -a
Darwin 8.10.1 Darwin Kernel Version 8.10.1: Wed May 23 16:33:00 PDT 2007;
root:xnu-792.22.5~1/RELEASE_I386 i386 i386

I have little experience with git, but if you need any more info, let me
know.  Any suggestion on what might be the problem, and how to fix it?

-Denis

^ permalink raw reply

* Re: [RFC/PATCH 2/2] gitweb: Add an option to show size of blobs in the tree view
From: Jakub Narebski @ 2007-07-31 23:07 UTC (permalink / raw)
  To: git
In-Reply-To: <11858807944170-git-send-email-jnareb@gmail.com>

Gaah, I just find that I forgot in some places to preserve "-l"...
Please wait for amended patch.

-- 
Jakub Narebski

^ permalink raw reply

* Re: [PATCH] Make verse of git-config manpage more readable
From: Johannes Schindelin @ 2007-07-31 22:36 UTC (permalink / raw)
  To: Alex Riesen; +Cc: Git Mailing List, Junio C Hamano
In-Reply-To: <20070731220117.GA3196@steel.home>

Hi,

On Wed, 1 Aug 2007, Alex Riesen wrote:

> Also mention '--file' in FILES.
> 
> Signed-off-by: Alex Riesen <raa.lkml@gmail.com>
> ---
> 
> Alex Riesen, Tue, Jul 31, 2007 14:02:13 +0200:
> > On 7/31/07, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> > > You could use this chance to make the verse nicer, i.e. split it into a
> > > [<file-option>] and an [<action>] part.
> 
> Did only the long one, file-option. I don't think the [<action>] part
> is essential, though. It is quite understandable...

It is too long.  That's my gripe with it.  But then, I do not really care; 
I know pretty well what "git config" can do...

Ciao,
Dscho

^ permalink raw reply

* Re: [RFC/PATCH 2/2] gitweb: Add an option to show size of blobs in the tree view
From: Junio C Hamano @ 2007-07-31 22:12 UTC (permalink / raw)
  To: Jakub Narebski; +Cc: git
In-Reply-To: <11858807944170-git-send-email-jnareb@gmail.com>

Jakub Narebski <jnareb@gmail.com> writes:

> It allows to play with 'tree' view with blob size. Currently you
> have to start browsing by adding ";opt=-l" to the gitweb URL by
> hand.  There is not link which will change view from ordinary 'tree'
> view to 'tree' view with blob sizes.
>
> The 'tree' with blob size view is slightly more costly than the
> ordinary, old 'tree' view, but not much more (0.018s vs 0.012s
> in the hot cache case), so I don't think we need to control it
> as a enabled (or disabled) feature, overrideable or not.  It
> probably should not be default.

I do not think there is any reason to forbid its use, as the
"-l" to ls-tree was introduced for exactly this purpose,
However, it might make sense to make the use of -l optional via
the %feature mechanism.  50% increase even on hot cache case is
not a price people who run busy sites would want to pay.

^ permalink raw reply

* Re: [PATCH] Add strbuf_printf() to do formatted printing to a strbuf.
From: Junio C Hamano @ 2007-07-31 22:01 UTC (permalink / raw)
  To: Kristian Høgsberg; +Cc: git
In-Reply-To: <11859116632279-git-send-email-krh@redhat.com>

Kristian Høgsberg <krh@redhat.com> writes:

> The old hardcoded limitation of just 2048 bytes wasn't too bad, considering
> that it's just the limit for one strbuf_printf invocation, not the total
> size of the strbuf contents.

I tend to agree with you now.  Sorry for the noise to make you
go to wild goose chase.  This does not look worth it.

^ permalink raw reply

* [PATCH] Make verse of git-config manpage more readable
From: Alex Riesen @ 2007-07-31 22:01 UTC (permalink / raw)
  To: Git Mailing List; +Cc: Johannes Schindelin, Junio C Hamano
In-Reply-To: <81b0412b0707310502x588c9c6dh7d9290360b27647a@mail.gmail.com>

Also mention '--file' in FILES.

Signed-off-by: Alex Riesen <raa.lkml@gmail.com>
---

Alex Riesen, Tue, Jul 31, 2007 14:02:13 +0200:
> On 7/31/07, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> > You could use this chance to make the verse nicer, i.e. split it into a
> > [<file-option>] and an [<action>] part.

Did only the long one, file-option. I don't think the [<action>] part
is essential, though. It is quite understandable...

 Documentation/git-config.txt |   32 +++++++++++++++++++-------------
 1 files changed, 19 insertions(+), 13 deletions(-)

diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index 88acf6c..8451ccc 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -9,17 +9,17 @@ git-config - Get and set repository or global options
 SYNOPSIS
 --------
 [verse]
-'git-config' [--system | --global | [-f|--file] config-file] [type] [-z|--null] name [value [value_regex]]
-'git-config' [--system | --global | [-f|--file] config-file] [type] --add name value
-'git-config' [--system | --global | [-f|--file] config-file] [type] --replace-all name [value [value_regex]]
-'git-config' [--system | --global | [-f|--file] config-file] [type] [-z|--null] --get name [value_regex]
-'git-config' [--system | --global | [-f|--file] config-file] [type] [-z|--null] --get-all name [value_regex]
-'git-config' [--system | --global | [-f|--file] config-file] [type] [-z|--null] --get-regexp name_regex [value_regex]
-'git-config' [--system | --global | [-f|--file] config-file] --unset name [value_regex]
-'git-config' [--system | --global | [-f|--file] config-file] --unset-all name [value_regex]
-'git-config' [--system | --global | [-f|--file] config-file] --rename-section old_name new_name
-'git-config' [--system | --global | [-f|--file] config-file] --remove-section name
-'git-config' [--system | --global | [-f|--file] config-file] [-z|--null] -l | --list
+'git-config' [<file-option>] [type] [-z|--null] name [value [value_regex]]
+'git-config' [<file-option>] [type] --add name value
+'git-config' [<file-option>] [type] --replace-all name [value [value_regex]]
+'git-config' [<file-option>] [type] [-z|--null] --get name [value_regex]
+'git-config' [<file-option>] [type] [-z|--null] --get-all name [value_regex]
+'git-config' [<file-option>] [type] [-z|--null] --get-regexp name_regex [value_regex]
+'git-config' [<file-option>] --unset name [value_regex]
+'git-config' [<file-option>] --unset-all name [value_regex]
+'git-config' [<file-option>] --rename-section old_name new_name
+'git-config' [<file-option>] --remove-section name
+'git-config' [<file-option>] [-z|--null] -l | --list
 
 DESCRIPTION
 -----------
@@ -40,6 +40,12 @@ convert the value to the canonical form (simple decimal number for int,
 a "true" or "false" string for bool).  If no type specifier is passed,
 no checks or transformations are performed on the value.
 
+The file-option can be one of '--system', '--global' or '--file'
+which specify where the values will be read from or written to.
+The default is to assume the config file of the current repository,
+.git/config unless defined otherwise with GIT_DIR and GIT_CONFIG
+(see <<FILES>>).
+
 This command will fail if:
 
 . The config file is invalid,
@@ -133,8 +139,8 @@ See also <<FILES>>.
 FILES
 -----
 
-There are three files where git-config will search for configuration
-options:
+If not set explicitely with '--file', there are three files where
+git-config will search for configuration options:
 
 .git/config::
 	Repository specific configuration file. (The filename is
-- 
1.5.3.rc3.116.g94fd8

^ permalink raw reply related

* Re: merge time
From: david @ 2007-07-31 20:07 UTC (permalink / raw)
  To: Steffen Prohaska
  Cc: Linus Torvalds, Jeff King, Shawn O. Pearce, Junio C Hamano,
	Matthew L Foster, git
In-Reply-To: <9DCA19D8-EDAE-4D04-A8C1-E0FF0A71DC93@zib.de>

main trunk is at commit X

developer clones Y and adds a patch to it

the merge from Y's tree to the main trunk is a fast-forward

David Lang

On Tue, 31 Jul 2007, Steffen Prohaska wrote:

> Date: Tue, 31 Jul 2007 20:06:24 +0200
> From: Steffen Prohaska <prohaska@zib.de>
> To: Linus Torvalds <torvalds@linux-foundation.org>
> Cc: Jeff King <peff@peff.net>, david@lang.hm,
>     Shawn O. Pearce <spearce@spearce.org>, Junio C Hamano <gitster@pobox.com>,
>     Matthew L Foster <mfoster167@yahoo.com>, git@vger.kernel.org
> Subject: Re: merge time
> 
>
> On Jul 30, 2007, at 7:42 PM, Linus Torvalds wrote:
>
>> On Mon, 30 Jul 2007, Jeff King wrote:
>> > 
>> > If you followed a strict policy of always merging topics to a "base"
>> > branch as your first parent, then never allowing fast forwards should
>> > allow a very easy-to-read history in gitk.
>> 
>> Only if only *one* person ever does any merges.
>> 
>> Immediately when you have other people merging code, you're now back in
>> the same boat.
>> 
>> This is why I personally think the whole policy of "no fast forward" is
>> totally broken. It's only usable in a non-distributed environment, where
>> there is one central person who does everything (a so-called "star
>> topology").
>
> I think no fast forward merges can always provide useful information.
>
> Public releases are often created from a public, linear branch that
> is not arbitrarily jumping around. Even if more than one person creates
> such releases there must be some agreement upon the responsibility for
> the release and thus for the branch the releases are created from.
>
> If all agreed to merge topics following the "no fast forward" policy and
> to use the release branch as a first parent, the commits along the first
> parents would document how commits came into the release branch. Non fast
> forward merges would always document that a series of commits was added
> to the release branch at once at the (local time) of the merge commit.
>
> If fast forwards are allowed it depends on whether the release branch can
> be fast-forwarded over a series of commits on a topic branch or not.
> In case of fast-forward, the history makes you believe each of the commits
> of the topic branch entered the release branch separately. It hides that
> they were all merged at once.
>
> I think, avoiding fast forwards may also be used to document a different
> level of quality required from commits to a topic branch and from commits
> to the release branch. A the time of the merge the tip of a topic branch
> must fulfill all quality requirements, for example compile on all platforms
> and pass all tests. But this need not be the case for every single commit
> on the topic branch. If you avoid fast forwards you still get a chain of
> commits along the first parent that would fulfill your quality requirements.
> If you allowed a fast forward over a low quality topic branch your chain
> might be broken and you can't be sure, which commits will have high quality
> and which one have a lower quality.
>
> You may argue that this is all crap because every single commit should be
> of highest quality, but I think this is unrealistic. Especially if more than
> one person is jointly working on a topic branch and less experienced and
> more experienced developers work together. It may also be rational to first
> get the basic functionality right and later polish work for several platforms
> and usage scenarios.
>
> Obviously other techniques, like rewriting a topic branch to a perfect commit
> series may be used as well. But from my understanding this may be hard if 
> more
> than a single person is involved. You could also squash the complete topic
> branch into a single commit but then valuable history might get lost.
>
> I don't see why what I have in mind would break if other people are merging
> code, too. They may or may not follow my local rules. But at the time of
> merging their changes, I can enforce my local quality policy and document
> this by create a merge commit.
>
> This could even be applied recursively. You could start to do a couple of
> merges but only thoroughly test the final result of the last merge. You could
> then enforce a link between the last very stable commit before you did all 
> the
> merges and the very well tested result after the final merge by enforcing a
> "no fast forward" merge.
>
> I'm not quite sure if such a history would be useful in the long term or
> may just become to complex. I'm also not sure if the artificial links would
> cause any trouble. I'm wondering, for example, if there are any cases in 
> which
> the additional links would make branching and merging harder.
>
> 	Steffen
>
>

^ permalink raw reply

* [PATCH] Add strbuf_printf() to do formatted printing to a strbuf.
From: Kristian Høgsberg @ 2007-07-31 19:54 UTC (permalink / raw)
  To: git; +Cc: gitster, Kristian Høgsberg
In-Reply-To: <7vhcnlgpeo.fsf@assigned-by-dhcp.cox.net>

Also, expose strbuf_add() and strbuf_add_char() to add raw data to the buffer.

Signed-off-by: Kristian Høgsberg <krh@redhat.com>
---

Ok, this gets uglier as we try to work around different versions of
vsnprintf.  On windows, vsnprintf returns -1 if the output doesn't fit in
the given buffer.  What we do is to keep doubling the buffer size until it
fits.  Not sure this is the best idea.

The old hardcoded limitation of just 2048 bytes wasn't too bad, considering
that it's just the limit for one strbuf_printf invocation, not the total
size of the strbuf contents.

I dunno...
Kristian

 strbuf.c |   69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------
 strbuf.h |    3 ++
 2 files changed, 65 insertions(+), 7 deletions(-)

diff --git a/strbuf.c b/strbuf.c
index e33d06b..2805c11 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -11,16 +11,26 @@ static void strbuf_begin(struct strbuf *sb) {
 	strbuf_init(sb);
 }
 
-static void inline strbuf_add(struct strbuf *sb, int ch) {
-	if (sb->alloc <= sb->len) {
-		sb->alloc = sb->alloc * 3 / 2 + 16;
-		sb->buf = xrealloc(sb->buf, sb->alloc);
-	}
+static void inline strbuf_grow(struct strbuf *sb, size_t extra)
+{
+	ALLOC_GROW(sb->buf, sb->len + extra, sb->alloc);
+}
+
+void strbuf_add(struct strbuf *sb, const char *data, size_t len)
+{
+	strbuf_grow(sb, len);
+	memcpy(sb->buf + sb->len, data, len);
+	sb->len += len;
+}
+
+void strbuf_add_char(struct strbuf *sb, int ch)
+{
+	strbuf_grow(sb, 1);
 	sb->buf[sb->len++] = ch;
 }
 
 static void strbuf_end(struct strbuf *sb) {
-	strbuf_add(sb, 0);
+	strbuf_add_char(sb, 0);
 }
 
 void read_line(struct strbuf *sb, FILE *fp, int term) {
@@ -33,9 +43,54 @@ void read_line(struct strbuf *sb, FILE *fp, int term) {
 	while ((ch = fgetc(fp)) != EOF) {
 		if (ch == term)
 			break;
-		strbuf_add(sb, ch);
+		strbuf_add_char(sb, ch);
 	}
 	if (ch == EOF && sb->len == 0)
 		sb->eof = 1;
 	strbuf_end(sb);
 }
+
+void strbuf_printf(struct strbuf *sb, const char *fmt, ...)
+{
+	char buffer[2048];
+	va_list args;
+	int len, size = 2 * sizeof buffer;
+
+	va_start(args, fmt);
+	len = vsnprintf(buffer, sizeof(buffer), fmt, args);
+	va_end(args);
+
+	if (len > sizeof(buffer)) {
+		/*
+		 * Didn't fit in the buffer, but this vsnprintf at
+		 * least gives us the required length back.  Grow the
+		 * buffer acccordingly and try again.
+		 */
+		strbuf_grow(sb, len);
+		va_start(args, fmt);
+		len = vsnprintf(sb->buf + sb->len,
+				sb->alloc - sb->len, fmt, args);
+		va_end(args);
+	} else if (len >= 0) {
+		/*
+		 * The initial vsnprintf fit in the temp buffer, just
+		 * copy it to the strbuf.
+		 */
+		strbuf_add(sb, buffer, len);
+	} else {
+		/*
+		 * This vnsprintf sucks and just returns -1 when the
+		 * buffer is too small.  Keep doubling the size until
+		 * it fits.
+		 */
+		while (len < 0) {
+			strbuf_grow(sb, size);
+			va_start(args, fmt);
+			len = vsnprintf(sb->buf + sb->len,
+					sb->alloc - sb->len, fmt, args);
+			va_end(args);
+			size *= 2;
+		}
+	}
+}
+
diff --git a/strbuf.h b/strbuf.h
index 74cc012..1e5d09e 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -9,5 +9,8 @@ struct strbuf {
 
 extern void strbuf_init(struct strbuf *);
 extern void read_line(struct strbuf *, FILE *, int);
+extern void strbuf_add(struct strbuf *sb, const char *data, size_t len);
+extern void strbuf_add_char(struct strbuf *sb, int ch);
+extern void strbuf_printf(struct strbuf *sb, const char *fmt, ...);
 
 #endif /* STRBUF_H */
-- 
1.5.2.GIT

^ permalink raw reply related

* Re: [PATCH] git.el: Support for incremental status updates.
From: Karl Hasselström @ 2007-07-31 19:58 UTC (permalink / raw)
  To: Alexandre Julliard; +Cc: David Kågedal, git
In-Reply-To: <874pjktnoe.fsf@wine.dyndns.org>

On 2007-07-31 20:48:01 +0200, Alexandre Julliard wrote:

> Yes, it's a new function. Something like this should work:
>
> From: Alexandre Julliard <julliard@winehq.org>
> Date: Tue, 31 Jul 2007 20:45:53 +0200
> Subject: [PATCH] git.el: Avoid using ewoc-set-data for compatibility with Emacs 21.
>
> Signed-off-by: Alexandre Julliard <julliard@winehq.org>

Acked-by: Karl Hasselström <kha@treskal.com>

I've only tested it on emacs 21, though.

-- 
Karl Hasselström, kha@treskal.com
      www.treskal.com/kalle

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox