* Do "git add" as a builtin
From: Linus Torvalds @ 2006-05-17 16:33 UTC (permalink / raw)
To: Junio C Hamano, Git Mailing List
First try. Let's see how well this works.
In many ways, the hard parts of "git commit" are not so different from
this, and a builtin commit would share a lot of the code, I think.
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
---
It passes the tests for me. I dropped the strange "--error-unmatch" test,
because it was really ugly and I didn't see the point. It seems to do all
the right things, but hey, a mistake here is obviously a bad thing.
I suspect that if I get around to "git commit", we'd be getting to the
point where most of the core/easy/often-used/whatever commands would be
all built-in.
diff --git a/Makefile b/Makefile
index f43ac63..e6f7794 100644
--- a/Makefile
+++ b/Makefile
@@ -218,7 +218,7 @@ LIB_OBJS = \
BUILTIN_OBJS = \
builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \
- builtin-grep.o
+ builtin-grep.o builtin-add.o
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
LIBS = $(GITLIBS) -lz
diff --git a/builtin-add.c b/builtin-add.c
new file mode 100644
index 0000000..e815b3d
--- /dev/null
+++ b/builtin-add.c
@@ -0,0 +1,228 @@
+/*
+ * "git add" builtin command
+ *
+ * Copyright (C) 2006 Linus Torvalds
+ */
+#include <fnmatch.h>
+
+#include "cache.h"
+#include "builtin.h"
+#include "dir.h"
+
+static const char builtin_add_usage[] =
+"git-add [-n] [-v] <filepattern>...";
+
+static int common_prefix(const char **pathspec)
+{
+ const char *path, *slash, *next;
+ int prefix;
+
+ if (!pathspec)
+ return 0;
+
+ path = *pathspec;
+ slash = strrchr(path, '/');
+ if (!slash)
+ return 0;
+
+ prefix = slash - path + 1;
+ while ((next = *++pathspec) != NULL) {
+ int len = strlen(next);
+ if (len >= prefix && !memcmp(path, next, len))
+ continue;
+ for (;;) {
+ if (!len)
+ return 0;
+ if (next[--len] != '/')
+ continue;
+ if (memcmp(path, next, len+1))
+ continue;
+ prefix = len + 1;
+ break;
+ }
+ }
+ return prefix;
+}
+
+static int match(const char **pathspec, const char *name, int namelen, int prefix)
+{
+ const char *match;
+
+ name += prefix;
+ namelen -= prefix;
+
+ while ((match = *pathspec++) != NULL) {
+ int matchlen;
+
+ match += prefix;
+ matchlen = strlen(match);
+ if (!matchlen)
+ return 1;
+ if (!strncmp(match, name, matchlen)) {
+ if (match[matchlen-1] == '/')
+ return 1;
+ switch (name[matchlen]) {
+ case '/': case '\0':
+ return 1;
+ }
+ }
+ if (!fnmatch(match, name, 0))
+ return 1;
+ }
+ return 0;
+}
+
+static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
+{
+ int i;
+ struct dir_entry **src, **dst;
+
+ src = dst = dir->entries;
+ i = dir->nr;
+ while (--i >= 0) {
+ struct dir_entry *entry = *src++;
+ if (!match(pathspec, entry->name, entry->len, prefix)) {
+ free(entry);
+ continue;
+ }
+ *dst++ = entry;
+ }
+ dir->nr = dst - dir->entries;
+}
+
+static void fill_directory(struct dir_struct *dir, const char **pathspec)
+{
+ const char *path, *base;
+ int baselen;
+
+ /* Set up the default git porcelain excludes */
+ memset(dir, 0, sizeof(*dir));
+ dir->exclude_per_dir = ".gitignore";
+ path = git_path("info/exclude");
+ if (!access(path, R_OK))
+ add_excludes_from_file(dir, path);
+
+ /*
+ * Calculate common prefix for the pathspec, and
+ * use that to optimize the directory walk
+ */
+ baselen = common_prefix(pathspec);
+ path = ".";
+ base = "";
+ if (baselen) {
+ char *common = xmalloc(baselen + 1);
+ common = xmalloc(baselen + 1);
+ memcpy(common, *pathspec, baselen);
+ common[baselen] = 0;
+ path = base = common;
+ }
+
+ /* Read the directory and prune it */
+ read_directory(dir, path, base, baselen);
+ if (pathspec)
+ prune_directory(dir, pathspec, baselen);
+}
+
+static int add_file_to_index(const char *path, int verbose)
+{
+ int size, namelen;
+ struct stat st;
+ struct cache_entry *ce;
+
+ if (lstat(path, &st))
+ die("%s: unable to stat (%s)", path, strerror(errno));
+
+ if (!S_ISREG(st.st_mode) && !S_ISLNK(st.st_mode))
+ die("%s: can only add regular files or symbolic links", path);
+
+ namelen = strlen(path);
+ size = cache_entry_size(namelen);
+ ce = xcalloc(1, size);
+ memcpy(ce->name, path, namelen);
+ ce->ce_flags = htons(namelen);
+ fill_stat_cache_info(ce, &st);
+
+ ce->ce_mode = create_ce_mode(st.st_mode);
+ if (!trust_executable_bit) {
+ /* If there is an existing entry, pick the mode bits
+ * from it.
+ */
+ int pos = cache_name_pos(path, namelen);
+ if (pos >= 0)
+ ce->ce_mode = active_cache[pos]->ce_mode;
+ }
+
+ if (index_path(ce->sha1, path, &st, 1))
+ die("unable to index file %s", path);
+ if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD))
+ die("unable to add %s to index",path);
+ if (verbose)
+ printf("add '%s'\n", path);
+ return 0;
+}
+
+static struct cache_file cache_file;
+
+int cmd_add(int argc, const char **argv, char **envp)
+{
+ int i, newfd;
+ int verbose = 0, show_only = 0;
+ const char *prefix = setup_git_directory();
+ const char **pathspec;
+ struct dir_struct dir;
+
+ git_config(git_default_config);
+
+ newfd = hold_index_file_for_update(&cache_file, get_index_file());
+ if (newfd < 0)
+ die("unable to create new cachefile");
+
+ if (read_cache() < 0)
+ die("index file corrupt");
+
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+
+ if (arg[0] != '-')
+ break;
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ if (!strcmp(arg, "-n")) {
+ show_only = 1;
+ continue;
+ }
+ if (!strcmp(arg, "-v")) {
+ verbose = 1;
+ continue;
+ }
+ die(builtin_add_usage);
+ }
+ git_config(git_default_config);
+ pathspec = get_pathspec(prefix, argv + i);
+
+ fill_directory(&dir, pathspec);
+
+ if (show_only) {
+ const char *sep = "", *eof = "";
+ for (i = 0; i < dir.nr; i++) {
+ printf("%s%s", sep, dir.entries[i]->name);
+ sep = " ";
+ eof = "\n";
+ }
+ fputs(eof, stdout);
+ return 0;
+ }
+
+ for (i = 0; i < dir.nr; i++)
+ add_file_to_index(dir.entries[i]->name, verbose);
+
+ if (active_cache_changed) {
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_index_file(&cache_file))
+ die("Unable to write new index file");
+ }
+
+ return 0;
+}
diff --git a/builtin.h b/builtin.h
index 7744f7d..1b77f4b 100644
--- a/builtin.h
+++ b/builtin.h
@@ -24,5 +24,6 @@ extern int cmd_count_objects(int argc, c
extern int cmd_push(int argc, const char **argv, char **envp);
extern int cmd_grep(int argc, const char **argv, char **envp);
+extern int cmd_add(int argc, const char **argv, char **envp);
#endif
diff --git a/git.c b/git.c
index a94d9ee..fac46af 100644
--- a/git.c
+++ b/git.c
@@ -50,6 +50,7 @@ static void handle_internal_command(int
{ "count-objects", cmd_count_objects },
{ "diff", cmd_diff },
{ "grep", cmd_grep },
+ { "add", cmd_add },
};
int i;
^ permalink raw reply related
* Re: Git 1.3.2 on Solaris
From: Linus Torvalds @ 2006-05-17 16:24 UTC (permalink / raw)
To: Stefan Pfetzing; +Cc: Git Mailing List
In-Reply-To: <f3d7535d0605170808l21d9f6d0gff1afaa10db17af9@mail.gmail.com>
On Wed, 17 May 2006, Stefan Pfetzing wrote:
>
> Ok, if I would do so, my prefix would be /usr/pkg, and the bindir would be
> /usr/pkg/bin. So I would need to have an xargs and so on symlink in
> /usr/pkg/bin.
> But this is simply not acceptable, because it breaks other NetBSD
> pkgsrc scripts.
DON'T USE /usr/pkg then.
Use /usr/pkg/git-core/share/ or something that is normally not on your
path.
And then install _just_ the "git" binary in /usr/pkg/bin.
That must be allowable by whatever solaris packaging rules: it's not like
other projects don't have their own internal library files.
Then you install the GNU symlinks under that same
/usr/pkg/git-core/share/bin
and you're all set. The only binary you can _see_ is "git", and when that
executes any scripts or other git binaries, it will set up the path to
include that magic hidden directory.
> Besides that, installing git to a different location is not an option
> for me, because I want to have git packaged by pkgsrc.
Now, I'm told pkgsrc is horrible, but it can't be so horrid as to not
allow private directories?
Linus
^ permalink raw reply
* Re: [ANNOUNCE] tig - text-mode interface for git
From: Jonas Fonseca @ 2006-05-17 15:53 UTC (permalink / raw)
To: Timo Hirvonen; +Cc: git
In-Reply-To: <20060517165305.07b682b2.tihirvon@gmail.com>
Timo Hirvonen <tihirvon@gmail.com> wrote Wed, May 17, 2006:
> Jonas Fonseca <fonseca@diku.dk> wrote:
>
> > Hello,
> >
> > I am pleased to announce tig, a simple git repository browser written
> > using ncurses. Basically, it just acts as a front-end for git-log and
> > git-show/git-diff. Additionally, you can also use it as a pager for git
> > commands.
> >
> > Currently, it just provides a minimum support for looking through
> > changes. I hope to slowly extend it to also be usable as a front-end for
> > git-blame and for tree browsing. Also, it doesn't do any fancy revision
> > graph rendering, but I want to at least give it a shot at some point. :)
>
> Thanks. This makes browsing repositories much easier. It would be
> great if the colors were customizable.
I am open to providing a better default color scheme, since my fondness
of green might be too dominating. For now you will have to edit the
colors defined under LINE_INFO, but sure. I think supporting something
similar to CG_COLORS will suffice.
--
Jonas Fonseca
^ permalink raw reply
* Git interfaces, frontends and other tools list
From: Jakub Narebski @ 2006-05-17 15:49 UTC (permalink / raw)
To: git
Authors of Git related tools are encouraged to enter the information about
those tools on GitWiki:
http://git.or.cz/gitwiki/InterfacesFrontendsAndTools
and, of course, correct and expand information there.
--
Jakub Narebski
Warsaw, Poland
^ permalink raw reply
* Re: [PATCH] builtin-grep: workaround for non GNU grep.
From: Bertrand Jacquin @ 2006-05-17 15:39 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Linus Torvalds, git
In-Reply-To: <7vves5geng.fsf_-_@assigned-by-dhcp.cox.net>
On 5/17/06, Junio C Hamano <junkio@cox.net> wrote:
> +ifdef NO_H_OPTION_IN_GREP
> + NO_H_OPTION_IN_GREP=1
> +else
> + NO_H_OPTION_IN_GREP=0
> +endif
> + if (NO_H_OPTION_IN_GREP)
> + push_arg("/dev/null");
> + else {
> + push_arg("-H");
> + push_arg("--");
> + }
Sorry, maybe a C code beginner question but while you define
NO_H_OPTION_IN_GREP in Makefile, why don't use a build time ``if''
instead of a runtime one ?
Like :
#if NO_H_OPTION_IN_GREP
push_arg("/dev/null");
#else
push_arg("-H");
push_arg("--");
#fi
--
Beber
#e.fr@freenode
^ permalink raw reply
* Re: [PATCH 1/2] Handle branch names with slashes
From: Catalin Marinas @ 2006-05-17 15:18 UTC (permalink / raw)
To: Karl Hasselström; +Cc: Junio C Hamano, git
In-Reply-To: <20060516074504.GA27234@diana.vm.bytemark.co.uk>
On 16/05/06, Karl Hasselström <kha@treskal.com> wrote:
> On 2006-05-15 23:48:04 -0700, Junio C Hamano wrote:
>
> > Karl Hasselström <kha@treskal.com> writes:
> >
> > > I had to change the patch@branch/top command-line syntax to
> > > patch@branch%top, in order to get sane parsing. The /top variant
> > > is still available for repositories that have no slashy branches;
> > > it is disabled as soon as there exists at least one subdirectory
> > > of refs/heads. Preferably, this compatibility hack can be killed
> > > some time in the future.
> >
> > I wonder if using double-slashes is an easier alternative to type
> > than '%', like "patch@branch//top". That way, you do not have to
> > forbid per-cent sign in branch names.
>
> Good argument. And // does look slightly better than %, too. But I'll
> wait a few days this time, or else someone will surely come along with
> an even better idea. :-)
I also thing // is a better solution than %.
There is another option, a bit more complicated, "patch@branch:top".
This would imply replacing : with .. in the current handling of the -r
option in diff/mail/export but it would make the commit or patch
ranges look more like GIT's arguments.
--
Catalin
^ permalink raw reply
* Re: Git 1.3.2 on Solaris
From: Stefan Pfetzing @ 2006-05-17 15:08 UTC (permalink / raw)
To: Git Mailing List
In-Reply-To: <Pine.LNX.4.64.0605170728520.10823@g5.osdl.org>
Hi Linus,
2006/5/17, Linus Torvalds <torvalds@osdl.org>:
>
> So let me just quote the thing you quoted but apparently didn't read:
[snip]
I did read that.
> There already _is_ such a directory. It's your "prefix=" directory plus
> "bin".
>
> So what you can do is make sure you compile with
>
> make prefix=/my/git/installation/prefix
>
> and then install the GNU tools in /my/git/installation/prefix/bin, and
> you're all set.
Ok, if I would do so, my prefix would be /usr/pkg, and the bindir would be
/usr/pkg/bin. So I would need to have an xargs and so on symlink in
/usr/pkg/bin.
But this is simply not acceptable, because it breaks other NetBSD
pkgsrc scripts.
Besides that, installing git to a different location is not an option
for me, because
I want to have git packaged by pkgsrc.
I suggest Junio's solution will work (gitexecdir) but I have to try
that later today.
> At most you might have to make some of the tests use "git xyzzy" instead
> of "git-xyzzy", and run "make install" before "make test".
>
> It wouldn't be wonderful, but hey, I've given alternatives (like using the
> GNU tools by default, or helping make git more portable in the first
> place). So it's a hack.
Yes I know, as far as I can, I'm willing to help with this.
bye
dreamind
--
http://www.dreamind.de/
Oroborus and Debian GNU/Linux Developer.
^ permalink raw reply
* Re: "git add $ignored_file" fail
From: Linus Torvalds @ 2006-05-17 14:49 UTC (permalink / raw)
To: Santi; +Cc: Alex Riesen, Johannes Schindelin, git
In-Reply-To: <8aa486160605170641p2ab8704o@mail.gmail.com>
On Wed, 17 May 2006, Santi wrote:
>
> In the other way, now I find the value of being able to say:
>
> $ git add t*
Exactly. The shell will expand the path, so the fact that the path exists
on the command line does not mean that "git add" should just trust it.
> and be sure that it does not add an ignored file. Unfortunately
> git-add cannot distinguish between both. So what I propose is to
> document it explicitly, something like:
Documenting this more clearly (and the workaround of forcing it with
"git-update-index --add" by hand) is certainly a good idea.
Linus
^ permalink raw reply
* Re: [RFC 5/5] Support 'master@2 hours ago' syntax
From: Linus Torvalds @ 2006-05-17 14:46 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Shawn Pearce, git
In-Reply-To: <7vbqtwhpum.fsf@assigned-by-dhcp.cox.net>
On Wed, 17 May 2006, Junio C Hamano wrote:
>
> This does not allow '2006-05-17 00:00:00' as the timespec, and
> the documentation carefully avoids giving that example, but I
> think it is better to spell that limitation out.
It doesn't? The "approxidate()" function should handle any reasonable date
specifier, and the above is certainly more than reasonable.
Why doesn't approxidate handle it?
Linus
^ permalink raw reply
* Re: git-add + git-reset --hard = Arrrggh!
From: Linus Torvalds @ 2006-05-17 14:43 UTC (permalink / raw)
To: Shawn Pearce; +Cc: git
In-Reply-To: <20060517094526.GA8563@spearce.org>
On Wed, 17 May 2006, Shawn Pearce wrote:
>
> Oh, and I totally agree with that discussion about GIT not clobbering
> files the user is working on which the user can't easily recover.
> I just wish recovery from the above stupidity didn't require going
> through .git/objects looking for the newest file. :-)
It doesn't.
Just do "git fsck-objects". It should have said
dangling blob <sha1>
and you can just do
git cat-file blob <sha1> > missing-file-name
and you're done.
That said, I think we should make everybody very aware of just how a
_dangerous_ operation "git reset --hard" is. It does delete your work. On
purpose. The fact that you can sometimes get it back doesn't change
anything.
(And I say "sometimes", because you'll obviously have lost all the
modifications that are only in your working tree - both to any added files
and to any old-time files. That's the _point_ of it, after all).
The default for "git reset" is to _not_ do any modifications in the
working tree. And there's a reason that's a default ;)
Linus
^ permalink raw reply
* Re: Git 1.3.2 on Solaris
From: Linus Torvalds @ 2006-05-17 14:33 UTC (permalink / raw)
To: Stefan Pfetzing; +Cc: Git Mailing List
In-Reply-To: <f3d7535d0605170105j2a6942cfh5a5a8a0d6153046f@mail.gmail.com>
On Wed, 17 May 2006, Stefan Pfetzing wrote:
>
> So I'd prefer a solution where one could set one flag for the Makefile of git,
> and git would check for the g prefix, create somewhere a directory with
> symlinks to the "real" gnu binaries and put it into $PATH upon startup of
> every git c-program or shellscript.
So let me just quote the thing you quoted but apparently didn't read:
> > If you use the git wrapper, for example, you can already depend on the
> > fact that it will prepend the git installation directory to the path, so
> > while the GNU tools might not _normally_ be on the path, if you put them
> > in the same directory as your git install, you'll automatically get them
> > as long as you use the "git cmd" format (rather than the "git-cmd"
> > format).
There already _is_ such a directory. It's your "prefix=" directory plus
"bin".
So what you can do is make sure you compile with
make prefix=/my/git/installation/prefix
and then install the GNU tools in /my/git/installation/prefix/bin, and
you're all set.
At most you might have to make some of the tests use "git xyzzy" instead
of "git-xyzzy", and run "make install" before "make test".
It wouldn't be wonderful, but hey, I've given alternatives (like using the
GNU tools by default, or helping make git more portable in the first
place). So it's a hack.
Linus
^ permalink raw reply
* Re: [PATCH] Implement git-quiltimport
From: Linus Torvalds @ 2006-05-17 14:28 UTC (permalink / raw)
To: Eric W. Biederman; +Cc: Junio C Hamano, git
In-Reply-To: <m13bf95ixo.fsf@ebiederm.dsl.xmission.com>
On Tue, 16 May 2006, Eric W. Biederman wrote:
>
> So what we need for this case really is a way to mark the
> commit objects in such a way that git-fetch or git-merge
> would choke on the commit objects and refuse to merge.
> That way the changes could not easily propagate in the wild.
> Every user would at least have to specify a non-default option,
> that Linus at least would never specify.
No.
What we need is for your git-quiltimport to not generate the bogus commits
in the first place!
Make it a damn error already.
If you want to have some stupid default to a non-existing and
non-supported user, then do
git-quiltimport --author="Bad User <baduser@nosuchuser.com>"
and get over it. Don't make the quiltimport script _default_ to a totally
idiotic model that will just screw people over.
Or keep it as your personal script. I'm just saying that it should sure as
hell not be used by any sane person in the format it is in now. Because,
_by_default_, it now does something insane, which is bad bad bad. Which
means that it shouldn't be merged by Junio.
Linus
^ permalink raw reply
* Re: [PATCH] builtin-grep: workaround for non GNU grep.
From: Linus Torvalds @ 2006-05-17 14:24 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vves5geng.fsf_-_@assigned-by-dhcp.cox.net>
No, please don't do it this way.
On Wed, 17 May 2006, Junio C Hamano wrote:
>
> + * Some grep implementations do not understand -H nor --
> + * but /dev/null can be used as a substitution in most
> + * cases.
> + *
> + * However -L and -c would slightly misbehave (-L would
> + * list /dev/null as a hit, and -c would report 0 hits
> + * from /dev/null); so do not use the external one on
> + * such platforms.
> */
> - if (!cached) {
> + if (!cached &&
> + (!NO_H_OPTION_IN_GREP ||
> + (!opt->count && !opt->unmatch_name_only))) {
> hit = external_grep(opt, paths, cached);
> if (hit >= 0)
> return hit;
That's the ugliest test ever, and at all the wrong levels.
Just make "external_grep()" test for the cases that it cannot handle, and
return -1. That's how it's designed to work.
Linus
^ permalink raw reply
* Re: [ANNOUNCE] tig - text-mode interface for git
From: Timo Hirvonen @ 2006-05-17 13:53 UTC (permalink / raw)
To: fonseca; +Cc: git
In-Reply-To: <20060517120733.GA14041@diku.dk>
Jonas Fonseca <fonseca@diku.dk> wrote:
> Hello,
>
> I am pleased to announce tig, a simple git repository browser written
> using ncurses. Basically, it just acts as a front-end for git-log and
> git-show/git-diff. Additionally, you can also use it as a pager for git
> commands.
>
> Currently, it just provides a minimum support for looking through
> changes. I hope to slowly extend it to also be usable as a front-end for
> git-blame and for tree browsing. Also, it doesn't do any fancy revision
> graph rendering, but I want to at least give it a shot at some point. :)
Thanks. This makes browsing repositories much easier. It would be
great if the colors were customizable.
--
http://onion.dynserv.net/~timo/
^ permalink raw reply
* Re: "git add $ignored_file" fail
From: Johannes Schindelin @ 2006-05-17 13:48 UTC (permalink / raw)
To: Santi; +Cc: Alex Riesen, git
In-Reply-To: <8aa486160605170641p2ab8704o@mail.gmail.com>
Hi,
On Wed, 17 May 2006, Santi wrote:
> In the other way, now I find the value of being able to say:
>
> $ git add t*
>
> and be sure that it does not add an ignored file. Unfortunately
> git-add cannot distinguish between both. So what I propose is to
> document it explicitly, something like:
>
> [...]
How about a "--force" AKA "-f" flag that overrides the ignore file?
I would implement it if I had time, but I am gone for three days in about
1 minute :-(
Ciao,
Dscho
^ permalink raw reply
* Re: "git add $ignored_file" fail
From: Jakub Narebski @ 2006-05-17 13:46 UTC (permalink / raw)
To: git
In-Reply-To: <8aa486160605170641p2ab8704o@mail.gmail.com>
Santi wrote:
> In the other way, now I find the value of being able to say:
>
> $ git add t*
>
> and be sure that it does not add an ignored file. Unfortunately
> git-add cannot distinguish between both.
Well, it could. If 'git add <filespec>' would result in NO files
added, take <filespec> as literate <file> (filename), regardless
of ignores.
--
Jakub Narebski
Warsaw, Poland
^ permalink raw reply
* Re: "git add $ignored_file" fail
From: Santi @ 2006-05-17 13:41 UTC (permalink / raw)
To: Alex Riesen; +Cc: Johannes Schindelin, git
In-Reply-To: <81b0412b0605170604i689a8f7axa5aeb7752dc72072@mail.gmail.com>
2006/5/17, Alex Riesen <raa.lkml@gmail.com>:
> On 5/17/06, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
> > >
> > > It's consistent from an implementation point of view, but not from the
> > > (my?) user point of view.
> >
> > So, you told git to ignore the file. And then you say "please add it". I
> > find it highly consistent that git does not do anything, because *you*
> > decided it should ignore it in the first place.
> >
>
> Well, he didn't say to ignore exactly this file. And Santi didn't know
> git-add uses git-ls-files here. So it actually is unexpected for a novice.
> It was unexpected for me too, until I looked into git-add.sh
>
Actually I'm not a novice, but that is the point.
In the other way, now I find the value of being able to say:
$ git add t*
and be sure that it does not add an ignored file. Unfortunately
git-add cannot distinguish between both. So what I propose is to
document it explicitly, something like:
diff --git a/Documentation/git-add.txt /Documentation/git-add.txt
index 5e31129..42f1e33 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -7,18 +7,20 @@ git-add - Add files to the index file
SYNOPSIS
--------
-'git-add' [-n] [-v] [--] <file>...
+'git-add' [-n] [-v] [--] <filespec>...
DESCRIPTION
-----------
A simple wrapper for git-update-index to add files to the index,
for people used to do "cvs add".
+It only adds non-ignored files, to add ignored files use
+"git-update-index --add".
OPTIONS
-------
-<file>...::
- Files to add to the index.
+<filespec>...::
+ Files to add to the index (see git-ls-files).
-n::
Don't actually add the file(s), just show if they exist.
diff --git a/git-add.sh b/git-add.sh
index d6a4bc7..394793f 100755
--- a/git-add.sh
+++ b/git-add.sh
@@ -1,6 +1,6 @@
#!/bin/sh
-USAGE='[-n] [-v] <file>...'
+USAGE='[-n] [-v] <filespec>...'
SUBDIRECTORY_OK='Yes'
. git-sh-setup
^ permalink raw reply related
* Re: [ANNOUNCE] tig - text-mode interface for git
From: Jakub Narebski @ 2006-05-17 12:41 UTC (permalink / raw)
To: git
In-Reply-To: <20060517120733.GA14041@diku.dk>
Jonas Fonseca wrote:
> Hello,
>
> I am pleased to announce tig, a simple git repository browser written
> using ncurses. Basically, it just acts as a front-end for git-log and
> git-show/git-diff. Additionally, you can also use it as a pager for git
> commands.
Thanks a lot.
Added to http://git.or.cz/gitwiki/InterfacesFrontendsAndTools
(under "Graphical Interfaces" section).
--
Jakub Narebski
Warsaw, Poland
^ permalink raw reply
* tig.c
From: Jonas Fonseca @ 2006-05-17 12:09 UTC (permalink / raw)
To: git
In-Reply-To: <20060517120733.GA14041@diku.dk>
/* Copyright (c) 2006 Jonas Fonseca <fonseca@diku.dk>
* See license info at the bottom. */
/**
* TIG(1)
* ======
*
* NAME
* ----
* tig - text-mode interface for git
*
* SYNOPSIS
* --------
* [verse]
* tig [options]
* tig [options] [--] [git log options]
* tig [options] log [git log options]
* tig [options] diff [git diff options]
* tig [options] show [git show options]
* tig [options] < [git command output]
*
* DESCRIPTION
* -----------
* Browse changes in a git repository. Additionally, tig(1) can also act
* as a pager for output of various git commands.
*
* When browsing repositories, tig(1) uses the underlying git commands
* to present the user with various views, such as summarized commit log
* and showing the commit with the log message, diffstat, and the diff.
*
* Using tig(1) as a pager, it will display input from stdin and try
* to colorize it.
**/
#ifndef VERSION
#define VERSION "tig-0.3"
#endif
#ifndef DEBUG
#define NDEBUG
#endif
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <curses.h>
static void die(const char *err, ...);
static void report(const char *msg, ...);
static void set_nonblocking_input(bool loading);
#define ABS(x) ((x) >= 0 ? (x) : -(x))
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
#define STRING_SIZE(x) (sizeof(x) - 1)
#define SIZEOF_REF 256 /* Size of symbolic or SHA1 ID. */
#define SIZEOF_CMD 1024 /* Size of command buffer. */
/* This color name can be used to refer to the default term colors. */
#define COLOR_DEFAULT (-1)
#define TIG_HELP "(d)iff, (l)og, (m)ain, (q)uit, (h)elp, (Enter) show diff"
/* The format and size of the date column in the main view. */
#define DATE_FORMAT "%Y-%m-%d %H:%M"
#define DATE_COLS STRING_SIZE("2006-04-29 14:21 ")
/* The default interval between line numbers. */
#define NUMBER_INTERVAL 1
#define TABSIZE 8
#define SCALE_SPLIT_VIEW(height) ((height) * 2 / 3)
/* Some ascii-shorthands fitted into the ncurses namespace. */
#define KEY_TAB '\t'
#define KEY_RETURN '\r'
#define KEY_ESC 27
/* User action requests. */
enum request {
/* Offset all requests to avoid conflicts with ncurses getch values. */
REQ_OFFSET = KEY_MAX + 1,
/* XXX: Keep the view request first and in sync with views[]. */
REQ_VIEW_MAIN,
REQ_VIEW_DIFF,
REQ_VIEW_LOG,
REQ_VIEW_HELP,
REQ_VIEW_PAGER,
REQ_ENTER,
REQ_QUIT,
REQ_PROMPT,
REQ_SCREEN_REDRAW,
REQ_SCREEN_RESIZE,
REQ_SCREEN_UPDATE,
REQ_SHOW_VERSION,
REQ_STOP_LOADING,
REQ_TOGGLE_LINE_NUMBERS,
REQ_VIEW_NEXT,
REQ_MOVE_UP,
REQ_MOVE_UP_ENTER,
REQ_MOVE_DOWN,
REQ_MOVE_DOWN_ENTER,
REQ_MOVE_PAGE_UP,
REQ_MOVE_PAGE_DOWN,
REQ_MOVE_FIRST_LINE,
REQ_MOVE_LAST_LINE,
REQ_SCROLL_LINE_UP,
REQ_SCROLL_LINE_DOWN,
REQ_SCROLL_PAGE_UP,
REQ_SCROLL_PAGE_DOWN,
};
struct ref {
char *name; /* Ref name; tag or head names are shortened. */
char id[41]; /* Commit SHA1 ID */
unsigned int tag:1; /* Is it a tag? */
unsigned int next:1; /* For ref lists: are there more refs? */
};
struct commit {
char id[41]; /* SHA1 ID. */
char title[75]; /* The first line of the commit message. */
char author[75]; /* The author of the commit. */
struct tm time; /* Date from the author ident. */
struct ref **refs; /* Repository references; tags & branch heads. */
};
/*
* String helpers
*/
static inline void
string_ncopy(char *dst, char *src, int dstlen)
{
strncpy(dst, src, dstlen - 1);
dst[dstlen - 1] = 0;
}
/* Shorthand for safely copying into a fixed buffer. */
#define string_copy(dst, src) \
string_ncopy(dst, src, sizeof(dst))
/* Shell quoting
*
* NOTE: The following is a slightly modified copy of the git project's shell
* quoting routines found in the quote.c file.
*
* Help to copy the thing properly quoted for the shell safety. any single
* quote is replaced with '\'', any exclamation point is replaced with '\!',
* and the whole thing is enclosed in a
*
* E.g.
* original sq_quote result
* name ==> name ==> 'name'
* a b ==> a b ==> 'a b'
* a'b ==> a'\''b ==> 'a'\''b'
* a!b ==> a'\!'b ==> 'a'\!'b'
*/
static size_t
sq_quote(char buf[SIZEOF_CMD], size_t bufsize, const char *src)
{
char c;
#define BUFPUT(x) ( (bufsize < SIZEOF_CMD) && (buf[bufsize++] = (x)) )
BUFPUT('\'');
while ((c = *src++)) {
if (c == '\'' || c == '!') {
BUFPUT('\'');
BUFPUT('\\');
BUFPUT(c);
BUFPUT('\'');
} else {
BUFPUT(c);
}
}
BUFPUT('\'');
return bufsize;
}
/**
* OPTIONS
* -------
**/
/* Option and state variables. */
static bool opt_line_number = FALSE;
static int opt_num_interval = NUMBER_INTERVAL;
static int opt_tab_size = TABSIZE;
static enum request opt_request = REQ_VIEW_MAIN;
static char opt_cmd[SIZEOF_CMD] = "";
static FILE *opt_pipe = NULL;
/* Returns the index of log or diff command or -1 to exit. */
static bool
parse_options(int argc, char *argv[])
{
int i;
for (i = 1; i < argc; i++) {
char *opt = argv[i];
/**
* -l::
* Start up in log view using the internal log command.
**/
if (!strcmp(opt, "-l")) {
opt_request = REQ_VIEW_LOG;
continue;
}
/**
* -d::
* Start up in diff view using the internal diff command.
**/
if (!strcmp(opt, "-d")) {
opt_request = REQ_VIEW_DIFF;
continue;
}
/**
* -n[INTERVAL], --line-number[=INTERVAL]::
* Prefix line numbers in log and diff view.
* Optionally, with interval different than each line.
**/
if (!strncmp(opt, "-n", 2) ||
!strncmp(opt, "--line-number", 13)) {
char *num = opt;
if (opt[1] == 'n') {
num = opt + 2;
} else if (opt[STRING_SIZE("--line-number")] == '=') {
num = opt + STRING_SIZE("--line-number=");
}
if (isdigit(*num))
opt_num_interval = atoi(num);
opt_line_number = TRUE;
continue;
}
/**
* -t[NSPACES], --tab-size[=NSPACES]::
* Set the number of spaces tabs should be expanded to.
**/
if (!strncmp(opt, "-t", 2) ||
!strncmp(opt, "--tab-size", 10)) {
char *num = opt;
if (opt[1] == 't') {
num = opt + 2;
} else if (opt[STRING_SIZE("--tab-size")] == '=') {
num = opt + STRING_SIZE("--tab-size=");
}
if (isdigit(*num))
opt_tab_size = MIN(atoi(num), TABSIZE);
continue;
}
/**
* -v, --version::
* Show version and exit.
**/
if (!strcmp(opt, "-v") ||
!strcmp(opt, "--version")) {
printf("tig version %s\n", VERSION);
return FALSE;
}
/**
* \--::
* End of tig(1) options. Useful when specifying command
* options for the main view. Example:
*
* $ tig -- --since=1.month
**/
if (!strcmp(opt, "--")) {
i++;
break;
}
/**
* log [git log options]::
* Open log view using the given git log options.
*
* diff [git diff options]::
* Open diff view using the given git diff options.
*
* show [git show options]::
* Open diff view using the given git show options.
**/
if (!strcmp(opt, "log") ||
!strcmp(opt, "diff") ||
!strcmp(opt, "show")) {
opt_request = opt[0] == 'l'
? REQ_VIEW_LOG : REQ_VIEW_DIFF;
break;
}
/**
* [git log options]::
* tig(1) will stop the option parsing when the first
* command line parameter not starting with "-" is
* encountered. All options including this one will be
* passed to git log when loading the main view.
* This makes it possible to say:
*
* $ tig tag-1.0..HEAD
**/
if (opt[0] && opt[0] != '-')
break;
die("unknown command '%s'", opt);
}
if (!isatty(STDIN_FILENO)) {
/**
* Pager mode
* ~~~~~~~~~~
* If stdin is a pipe, any log or diff options will be ignored and the
* pager view will be opened loading data from stdin. The pager mode
* can be used for colorizing output from various git commands.
*
* Example on how to colorize the output of git-show(1):
*
* $ git show | tig
**/
opt_request = REQ_VIEW_PAGER;
opt_pipe = stdin;
} else if (i < argc) {
size_t buf_size;
/**
* Git command options
* ~~~~~~~~~~~~~~~~~~~
* All git command options specified on the command line will
* be passed to the given command and all will be shell quoted
* before they are passed to the shell.
*
* NOTE: If you specify options for the main view, you should
* not use the `--pretty` option as this option will be set
* automatically to the format expected by the main view.
*
* Example on how to open the log view and show both author and
* committer information:
*
* $ tig log --pretty=fuller
*
* See the <<refspec, "Specifying revisions">> section below
* for an introduction to revision options supported by the git
* commands. For details on specific git command options, refer
* to the man page of the command in question.
**/
if (opt_request == REQ_VIEW_MAIN)
/* XXX: This is vulnerable to the user overriding
* options required for the main view parser. */
string_copy(opt_cmd, "git log --stat --pretty=raw");
else
string_copy(opt_cmd, "git");
buf_size = strlen(opt_cmd);
while (buf_size < sizeof(opt_cmd) && i < argc) {
opt_cmd[buf_size++] = ' ';
buf_size = sq_quote(opt_cmd, buf_size, argv[i++]);
}
if (buf_size >= sizeof(opt_cmd))
die("command too long");
opt_cmd[buf_size] = 0;
}
return TRUE;
}
/*
* Line-oriented content detection.
*/
#define LINE_INFO \
/* Line type String to match Foreground Background Attributes
* --------- --------------- ---------- ---------- ---------- */ \
/* Diff markup */ \
LINE(DIFF, "diff --git ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(DIFF_INDEX, "index ", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(DIFF_CHUNK, "@@", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(DIFF_ADD, "+", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(DIFF_DEL, "-", COLOR_RED, COLOR_DEFAULT, 0), \
LINE(DIFF_OLDMODE, "old file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(DIFF_NEWMODE, "new file mode ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(DIFF_COPY, "copy ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(DIFF_RENAME, "rename ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(DIFF_SIM, "similarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(DIFF_DISSIM, "dissimilarity ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
/* Pretty print commit header */ \
LINE(PP_AUTHOR, "Author: ", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(PP_COMMIT, "Commit: ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(PP_MERGE, "Merge: ", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(PP_DATE, "Date: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(PP_ADATE, "AuthorDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
LINE(PP_CDATE, "CommitDate: ", COLOR_YELLOW, COLOR_DEFAULT, 0), \
/* Raw commit header */ \
LINE(COMMIT, "commit ", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(PARENT, "parent ", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(TREE, "tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(AUTHOR, "author ", COLOR_CYAN, COLOR_DEFAULT, 0), \
LINE(COMMITTER, "committer ", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
/* Misc */ \
LINE(DIFF_TREE, "diff-tree ", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(SIGNOFF, " Signed-off-by", COLOR_YELLOW, COLOR_DEFAULT, 0), \
/* UI colors */ \
LINE(DEFAULT, "", COLOR_DEFAULT, COLOR_DEFAULT, A_NORMAL), \
LINE(CURSOR, "", COLOR_WHITE, COLOR_GREEN, A_BOLD), \
LINE(STATUS, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(TITLE_BLUR, "", COLOR_WHITE, COLOR_BLUE, 0), \
LINE(TITLE_FOCUS, "", COLOR_WHITE, COLOR_BLUE, A_BOLD), \
LINE(MAIN_DATE, "", COLOR_BLUE, COLOR_DEFAULT, 0), \
LINE(MAIN_AUTHOR, "", COLOR_GREEN, COLOR_DEFAULT, 0), \
LINE(MAIN_COMMIT, "", COLOR_DEFAULT, COLOR_DEFAULT, 0), \
LINE(MAIN_DELIM, "", COLOR_MAGENTA, COLOR_DEFAULT, 0), \
LINE(MAIN_TAG, "", COLOR_MAGENTA, COLOR_DEFAULT, A_BOLD), \
LINE(MAIN_REF, "", COLOR_CYAN, COLOR_DEFAULT, A_BOLD),
enum line_type {
#define LINE(type, line, fg, bg, attr) \
LINE_##type
LINE_INFO
#undef LINE
};
struct line_info {
char *line; /* The start of line to match. */
int linelen; /* Size of string to match. */
int fg, bg, attr; /* Color and text attributes for the lines. */
};
static struct line_info line_info[] = {
#define LINE(type, line, fg, bg, attr) \
{ (line), STRING_SIZE(line), (fg), (bg), (attr) }
LINE_INFO
#undef LINE
};
static enum line_type
get_line_type(char *line)
{
int linelen = strlen(line);
enum line_type type;
for (type = 0; type < ARRAY_SIZE(line_info); type++)
/* Case insensitive search matches Signed-off-by lines better. */
if (linelen >= line_info[type].linelen &&
!strncasecmp(line_info[type].line, line, line_info[type].linelen))
return type;
return LINE_DEFAULT;
}
static inline int
get_line_attr(enum line_type type)
{
assert(type < ARRAY_SIZE(line_info));
return COLOR_PAIR(type) | line_info[type].attr;
}
static void
init_colors(void)
{
int default_bg = COLOR_BLACK;
int default_fg = COLOR_WHITE;
enum line_type type;
start_color();
if (use_default_colors() != ERR) {
default_bg = -1;
default_fg = -1;
}
for (type = 0; type < ARRAY_SIZE(line_info); type++) {
struct line_info *info = &line_info[type];
int bg = info->bg == COLOR_DEFAULT ? default_bg : info->bg;
int fg = info->fg == COLOR_DEFAULT ? default_fg : info->fg;
init_pair(type, fg, bg);
}
}
/**
* ENVIRONMENT VARIABLES
* ---------------------
* Several options related to the interface with git can be configured
* via environment options.
*
* Repository references
* ~~~~~~~~~~~~~~~~~~~~~
* Commits that are referenced by tags and branch heads will be marked
* by the reference name surrounded by '[' and ']':
*
* 2006-03-26 19:42 Petr Baudis | [cogito-0.17.1] Cogito 0.17.1
*
* If you want to filter out certain directories under `.git/refs/`, say
* `tmp` you can do it by setting the following variable:
*
* $ TIG_LS_REMOTE="git ls-remote . | sed /\/tmp\//d" tig
*
* Or set the variable permanently in your environment.
*
* TIG_LS_REMOTE::
* Set command for retrieving all repository references. The command
* should output data in the same format as git-ls-remote(1).
**/
#define TIG_LS_REMOTE \
"git ls-remote . 2>/dev/null"
/**
* [[view-commands]]
* View commands
* ~~~~~~~~~~~~~
* It is possible to alter which commands are used for the different views.
* If for example you prefer commits in the main view to be sorted by date
* and only show 500 commits, use:
*
* $ TIG_MAIN_CMD="git log --date-order -n500 --pretty=raw %s" tig
*
* Or set the variable permanently in your environment.
*
* Notice, how `%s` is used to specify the commit reference. There can
* be a maximum of 5 `%s` ref specifications.
*
* TIG_DIFF_CMD::
* The command used for the diff view. By default, git show is used
* as a backend.
*
* TIG_LOG_CMD::
* The command used for the log view. If you prefer to have both
* author and committer shown in the log view be sure to pass
* `--pretty=fuller` to git log.
*
* TIG_MAIN_CMD::
* The command used for the main view. Note, you must always specify
* the option: `--pretty=raw` since the main view parser expects to
* read that format.
**/
#define TIG_DIFF_CMD \
"git show --patch-with-stat --find-copies-harder -B -C %s"
#define TIG_LOG_CMD \
"git log --cc --stat -n100 %s"
#define TIG_MAIN_CMD \
"git log --topo-order --stat --pretty=raw %s"
/* ... silently ignore that the following are also exported. */
#define TIG_HELP_CMD \
"man tig 2>/dev/null"
#define TIG_PAGER_CMD \
""
/**
* The viewer
* ----------
*
* tig(1) presents various 'views' of a repository. Each view is based on output
* from an external command, most often 'git log', 'git diff', or 'git show'.
*
* The main view::
* Is the default view, and it shows a one line summary of each commit
* in the chosen list of revisions. The summary includes commit date,
* author, and the first line of the log message. Additionally, any
* repository references, such as tags, will be shown.
*
* The log view::
* Presents a more rich view of the revision log showing the whole log
* message and the diffstat.
*
* The diff view::
* Shows either the diff of the current working tree, that is, what
* has changed since the last commit, or the commit diff complete
* with log message, diffstat and diff.
*
* The pager view::
* Is used for displaying both input from stdin and output from git
* commands entered in the internal prompt.
*
* The help view::
* Displays the information from the tig(1) man page. For the help view
* to work you need to have the tig(1) man page installed.
**/
struct view {
const char *name; /* View name */
char *cmd_fmt; /* Default command line format */
char *cmd_env; /* Command line set via environment */
char *id; /* Points to either of ref_{head,commit} */
size_t objsize; /* Size of objects in the line index */
struct view_ops {
/* What type of content being displayed. Used in the
* title bar. */
char *type;
/* Draw one line; @lineno must be < view->height. */
bool (*draw)(struct view *view, unsigned int lineno);
/* Read one line; updates view->line. */
bool (*read)(struct view *view, char *line);
/* Depending on view, change display based on current line. */
bool (*enter)(struct view *view);
} *ops;
char cmd[SIZEOF_CMD]; /* Command buffer */
char ref[SIZEOF_REF]; /* Hovered commit reference */
char vid[SIZEOF_REF]; /* View ID. Set to id member when updating. */
int height, width; /* The width and height of the main window */
WINDOW *win; /* The main window */
WINDOW *title; /* The title window living below the main window */
/* Navigation */
unsigned long offset; /* Offset of the window top */
unsigned long lineno; /* Current line number */
/* Buffering */
unsigned long lines; /* Total number of lines */
void **line; /* Line index; each line contains user data */
unsigned int digits; /* Number of digits in the lines member. */
/* Loading */
FILE *pipe;
time_t start_time;
};
static struct view_ops pager_ops;
static struct view_ops main_ops;
static char ref_head[SIZEOF_REF] = "HEAD";
static char ref_commit[SIZEOF_REF] = "HEAD";
#define VIEW_STR(name, cmd, env, ref, objsize, ops) \
{ name, cmd, #env, ref, objsize, ops }
#define VIEW_(id, name, ops, ref, objsize) \
VIEW_STR(name, TIG_##id##_CMD, TIG_##id##_CMD, ref, objsize, ops)
static struct view views[] = {
VIEW_(MAIN, "main", &main_ops, ref_head, sizeof(struct commit)),
VIEW_(DIFF, "diff", &pager_ops, ref_commit, sizeof(char)),
VIEW_(LOG, "log", &pager_ops, ref_head, sizeof(char)),
VIEW_(HELP, "help", &pager_ops, ref_head, sizeof(char)),
VIEW_(PAGER, "pager", &pager_ops, "static", sizeof(char)),
};
#define VIEW(req) (&views[(req) - REQ_OFFSET - 1])
/* The display array of active views and the index of the current view. */
static struct view *display[2];
static unsigned int current_view;
#define foreach_view(view, i) \
for (i = 0; i < ARRAY_SIZE(display) && (view = display[i]); i++)
static void
redraw_view_from(struct view *view, int lineno)
{
assert(0 <= lineno && lineno < view->height);
for (; lineno < view->height; lineno++) {
if (!view->ops->draw(view, lineno))
break;
}
redrawwin(view->win);
wrefresh(view->win);
}
static void
redraw_view(struct view *view)
{
wclear(view->win);
redraw_view_from(view, 0);
}
static void
resize_display(void)
{
int offset, i;
struct view *base = display[0];
struct view *view = display[1] ? display[1] : display[0];
/* Setup window dimensions */
getmaxyx(stdscr, base->height, base->width);
/* Make room for the status window. */
base->height -= 1;
if (view != base) {
/* Horizontal split. */
view->width = base->width;
view->height = SCALE_SPLIT_VIEW(base->height);
base->height -= view->height;
/* Make room for the title bar. */
view->height -= 1;
}
/* Make room for the title bar. */
base->height -= 1;
offset = 0;
foreach_view (view, i) {
/* Keep the size of the all view windows one lager than is
* required. This makes current line management easier when the
* cursor will go outside the window. */
if (!view->win) {
view->win = newwin(view->height + 1, 0, offset, 0);
if (!view->win)
die("Failed to create %s view", view->name);
scrollok(view->win, TRUE);
view->title = newwin(1, 0, offset + view->height, 0);
if (!view->title)
die("Failed to create title window");
} else {
wresize(view->win, view->height + 1, view->width);
mvwin(view->win, offset, 0);
mvwin(view->title, offset + view->height, 0);
wrefresh(view->win);
}
offset += view->height + 1;
}
}
static void
update_view_title(struct view *view)
{
if (view == display[current_view])
wbkgdset(view->title, get_line_attr(LINE_TITLE_FOCUS));
else
wbkgdset(view->title, get_line_attr(LINE_TITLE_BLUR));
werase(view->title);
wmove(view->title, 0, 0);
/* [main] ref: 334b506... - commit 6 of 4383 (0%) */
if (*view->ref)
wprintw(view->title, "[%s] %s", view->name, view->ref);
else
wprintw(view->title, "[%s]", view->name);
if (view->lines) {
wprintw(view->title, " - %s %d of %d (%d%%)",
view->ops->type,
view->lineno + 1,
view->lines,
(view->lineno + 1) * 100 / view->lines);
}
wrefresh(view->title);
}
/*
* Navigation
*/
/* Scrolling backend */
static void
do_scroll_view(struct view *view, int lines)
{
/* The rendering expects the new offset. */
view->offset += lines;
assert(0 <= view->offset && view->offset < view->lines);
assert(lines);
/* Redraw the whole screen if scrolling is pointless. */
if (view->height < ABS(lines)) {
redraw_view(view);
} else {
int line = lines > 0 ? view->height - lines : 0;
int end = line + ABS(lines);
wscrl(view->win, lines);
for (; line < end; line++) {
if (!view->ops->draw(view, line))
break;
}
}
/* Move current line into the view. */
if (view->lineno < view->offset) {
view->lineno = view->offset;
view->ops->draw(view, 0);
} else if (view->lineno >= view->offset + view->height) {
if (view->lineno == view->offset + view->height) {
/* Clear the hidden line so it doesn't show if the view
* is scrolled up. */
wmove(view->win, view->height, 0);
wclrtoeol(view->win);
}
view->lineno = view->offset + view->height - 1;
view->ops->draw(view, view->lineno - view->offset);
}
assert(view->offset <= view->lineno && view->lineno < view->lines);
redrawwin(view->win);
wrefresh(view->win);
report("");
}
/* Scroll frontend */
static void
scroll_view(struct view *view, enum request request)
{
int lines = 1;
switch (request) {
case REQ_SCROLL_PAGE_DOWN:
lines = view->height;
case REQ_SCROLL_LINE_DOWN:
if (view->offset + lines > view->lines)
lines = view->lines - view->offset;
if (lines == 0 || view->offset + view->height >= view->lines) {
report("Cannot scroll beyond the last line");
return;
}
break;
case REQ_SCROLL_PAGE_UP:
lines = view->height;
case REQ_SCROLL_LINE_UP:
if (lines > view->offset)
lines = view->offset;
if (lines == 0) {
report("Cannot scroll beyond the first line");
return;
}
lines = -lines;
break;
default:
die("request %d not handled in switch", request);
}
do_scroll_view(view, lines);
}
/* Cursor moving */
static void
move_view(struct view *view, enum request request)
{
int steps;
switch (request) {
case REQ_MOVE_FIRST_LINE:
steps = -view->lineno;
break;
case REQ_MOVE_LAST_LINE:
steps = view->lines - view->lineno - 1;
break;
case REQ_MOVE_PAGE_UP:
steps = view->height > view->lineno
? -view->lineno : -view->height;
break;
case REQ_MOVE_PAGE_DOWN:
steps = view->lineno + view->height >= view->lines
? view->lines - view->lineno - 1 : view->height;
break;
case REQ_MOVE_UP:
case REQ_MOVE_UP_ENTER:
steps = -1;
break;
case REQ_MOVE_DOWN:
case REQ_MOVE_DOWN_ENTER:
steps = 1;
break;
default:
die("request %d not handled in switch", request);
}
if (steps <= 0 && view->lineno == 0) {
report("Cannot move beyond the first line");
return;
} else if (steps >= 0 && view->lineno + 1 >= view->lines) {
report("Cannot move beyond the last line");
return;
}
/* Move the current line */
view->lineno += steps;
assert(0 <= view->lineno && view->lineno < view->lines);
/* Repaint the old "current" line if we be scrolling */
if (ABS(steps) < view->height) {
int prev_lineno = view->lineno - steps - view->offset;
wmove(view->win, prev_lineno, 0);
wclrtoeol(view->win);
view->ops->draw(view, prev_lineno);
}
/* Check whether the view needs to be scrolled */
if (view->lineno < view->offset ||
view->lineno >= view->offset + view->height) {
if (steps < 0 && -steps > view->offset) {
steps = -view->offset;
} else if (steps > 0) {
if (view->lineno == view->lines - 1 &&
view->lines > view->height) {
steps = view->lines - view->offset - 1;
if (steps >= view->height)
steps -= view->height - 1;
}
}
do_scroll_view(view, steps);
return;
}
/* Draw the current line */
view->ops->draw(view, view->lineno - view->offset);
redrawwin(view->win);
wrefresh(view->win);
report("");
}
/*
* Incremental updating
*/
static bool
begin_update(struct view *view)
{
char *id = view->id;
if (opt_cmd[0]) {
string_copy(view->cmd, opt_cmd);
opt_cmd[0] = 0;
/* When running random commands, the view ref could have become
* invalid so clear it. */
view->ref[0] = 0;
} else {
char *format = view->cmd_env ? view->cmd_env : view->cmd_fmt;
if (snprintf(view->cmd, sizeof(view->cmd), format,
id, id, id, id, id) >= sizeof(view->cmd))
return FALSE;
}
/* Special case for the pager view. */
if (opt_pipe) {
view->pipe = opt_pipe;
opt_pipe = NULL;
} else {
view->pipe = popen(view->cmd, "r");
}
if (!view->pipe)
return FALSE;
set_nonblocking_input(TRUE);
view->offset = 0;
view->lines = 0;
view->lineno = 0;
string_copy(view->vid, id);
if (view->line) {
int i;
for (i = 0; i < view->lines; i++)
if (view->line[i])
free(view->line[i]);
free(view->line);
view->line = NULL;
}
view->start_time = time(NULL);
return TRUE;
}
static void
end_update(struct view *view)
{
if (!view->pipe)
return;
set_nonblocking_input(FALSE);
if (view->pipe == stdin)
fclose(view->pipe);
else
pclose(view->pipe);
view->pipe = NULL;
}
static bool
update_view(struct view *view)
{
char buffer[BUFSIZ];
char *line;
void **tmp;
/* The number of lines to read. If too low it will cause too much
* redrawing (and possible flickering), if too high responsiveness
* will suffer. */
unsigned long lines = view->height;
int redraw_from = -1;
if (!view->pipe)
return TRUE;
/* Only redraw if lines are visible. */
if (view->offset + view->height >= view->lines)
redraw_from = view->lines - view->offset;
tmp = realloc(view->line, sizeof(*view->line) * (view->lines + lines));
if (!tmp)
goto alloc_error;
view->line = tmp;
while ((line = fgets(buffer, sizeof(buffer), view->pipe))) {
int linelen = strlen(line);
if (linelen)
line[linelen - 1] = 0;
if (!view->ops->read(view, line))
goto alloc_error;
if (lines-- == 1)
break;
}
{
int digits;
lines = view->lines;
for (digits = 0; lines; digits++)
lines /= 10;
/* Keep the displayed view in sync with line number scaling. */
if (digits != view->digits) {
view->digits = digits;
redraw_from = 0;
}
}
if (redraw_from >= 0) {
/* If this is an incremental update, redraw the previous line
* since for commits some members could have changed when
* loading the main view. */
if (redraw_from > 0)
redraw_from--;
/* Incrementally draw avoids flickering. */
redraw_view_from(view, redraw_from);
}
/* Update the title _after_ the redraw so that if the redraw picks up a
* commit reference in view->ref it'll be available here. */
update_view_title(view);
if (ferror(view->pipe)) {
report("Failed to read: %s", strerror(errno));
goto end;
} else if (feof(view->pipe)) {
time_t secs = time(NULL) - view->start_time;
if (view == VIEW(REQ_VIEW_HELP)) {
char *msg = TIG_HELP;
if (view->lines == 0) {
/* Slightly ugly, but abusing view->ref keeps
* the error message. */
string_copy(view->ref, "No help available");
msg = "The tig(1) manpage is not installed";
}
report("%s", msg);
goto end;
}
report("Loaded %d lines in %ld second%s", view->lines, secs,
secs == 1 ? "" : "s");
goto end;
}
return TRUE;
alloc_error:
report("Allocation failure");
end:
end_update(view);
return FALSE;
}
enum open_flags {
OPEN_DEFAULT = 0, /* Use default view switching. */
OPEN_SPLIT = 1, /* Split current view. */
OPEN_BACKGROUNDED = 2, /* Backgrounded. */
OPEN_RELOAD = 4, /* Reload view even if it is the current. */
};
static void
open_view(struct view *prev, enum request request, enum open_flags flags)
{
bool backgrounded = !!(flags & OPEN_BACKGROUNDED);
bool split = !!(flags & OPEN_SPLIT);
bool reload = !!(flags & OPEN_RELOAD);
struct view *view = VIEW(request);
struct view *displayed;
int nviews;
/* Cycle between displayed views and count the views. */
foreach_view (displayed, nviews) {
if (prev != view &&
view == displayed &&
!strcmp(view->vid, prev->vid)) {
current_view = nviews;
/* Blur out the title of the previous view. */
update_view_title(prev);
report("");
return;
}
}
if (view == prev && nviews == 1 && !reload) {
report("Already in %s view", view->name);
return;
}
if ((reload || strcmp(view->vid, view->id)) &&
!begin_update(view)) {
report("Failed to load %s view", view->name);
return;
}
if (split) {
display[current_view + 1] = view;
if (!backgrounded)
current_view++;
} else {
/* Maximize the current view. */
memset(display, 0, sizeof(display));
current_view = 0;
display[current_view] = view;
}
resize_display();
if (split && prev->lineno - prev->offset >= prev->height) {
/* Take the title line into account. */
int lines = prev->lineno - prev->offset - prev->height + 1;
/* Scroll the view that was split if the current line is
* outside the new limited view. */
do_scroll_view(prev, lines);
}
if (prev && view != prev) {
/* "Blur" the previous view. */
if (!backgrounded)
update_view_title(prev);
/* Continue loading split views in the background. */
if (!split)
end_update(prev);
}
if (view->pipe) {
/* Clear the old view and let the incremental updating refill
* the screen. */
wclear(view->win);
report("Loading...");
} else {
redraw_view(view);
if (view == VIEW(REQ_VIEW_HELP))
report("%s", TIG_HELP);
else
report("");
}
/* If the view is backgrounded the above calls to report()
* won't redraw the view title. */
if (backgrounded)
update_view_title(view);
}
/*
* User request switch noodle
*/
static int
view_driver(struct view *view, enum request request)
{
int i;
switch (request) {
case REQ_MOVE_UP:
case REQ_MOVE_DOWN:
case REQ_MOVE_PAGE_UP:
case REQ_MOVE_PAGE_DOWN:
case REQ_MOVE_FIRST_LINE:
case REQ_MOVE_LAST_LINE:
move_view(view, request);
break;
case REQ_SCROLL_LINE_DOWN:
case REQ_SCROLL_LINE_UP:
case REQ_SCROLL_PAGE_DOWN:
case REQ_SCROLL_PAGE_UP:
scroll_view(view, request);
break;
case REQ_VIEW_MAIN:
case REQ_VIEW_DIFF:
case REQ_VIEW_LOG:
case REQ_VIEW_HELP:
case REQ_VIEW_PAGER:
open_view(view, request, OPEN_DEFAULT);
break;
case REQ_MOVE_UP_ENTER:
case REQ_MOVE_DOWN_ENTER:
move_view(view, request);
/* Fall-through */
case REQ_ENTER:
if (!view->lines) {
report("Nothing to enter");
break;
}
return view->ops->enter(view);
case REQ_VIEW_NEXT:
{
int nviews = display[1] ? 2 : 1;
int next_view = (current_view + 1) % nviews;
if (next_view == current_view) {
report("Only one view is displayed");
break;
}
current_view = next_view;
/* Blur out the title of the previous view. */
update_view_title(view);
report("");
break;
}
case REQ_TOGGLE_LINE_NUMBERS:
opt_line_number = !opt_line_number;
redraw_view(view);
update_view_title(view);
break;
case REQ_PROMPT:
/* Always reload^Wrerun commands from the prompt. */
open_view(view, opt_request, OPEN_RELOAD);
break;
case REQ_STOP_LOADING:
foreach_view (view, i) {
if (view->pipe)
report("Stopped loaded the %s view", view->name),
end_update(view);
}
break;
case REQ_SHOW_VERSION:
report("Version: %s", VERSION);
return TRUE;
case REQ_SCREEN_RESIZE:
resize_display();
/* Fall-through */
case REQ_SCREEN_REDRAW:
foreach_view (view, i) {
redraw_view(view);
update_view_title(view);
}
break;
case REQ_SCREEN_UPDATE:
doupdate();
return TRUE;
case REQ_QUIT:
return FALSE;
default:
/* An unknown key will show most commonly used commands. */
report("Unknown key, press 'h' for help");
return TRUE;
}
return TRUE;
}
/*
* View backend handlers
*/
static bool
pager_draw(struct view *view, unsigned int lineno)
{
enum line_type type;
char *line;
int linelen;
int attr;
if (view->offset + lineno >= view->lines)
return FALSE;
line = view->line[view->offset + lineno];
type = get_line_type(line);
wmove(view->win, lineno, 0);
if (view->offset + lineno == view->lineno) {
if (type == LINE_COMMIT) {
string_copy(view->ref, line + 7);
string_copy(ref_commit, view->ref);
}
type = LINE_CURSOR;
wchgat(view->win, -1, 0, type, NULL);
}
attr = get_line_attr(type);
wattrset(view->win, attr);
linelen = strlen(line);
if (opt_line_number || opt_tab_size < TABSIZE) {
static char spaces[] = " ";
int col_offset = 0, col = 0;
if (opt_line_number) {
unsigned long real_lineno = view->offset + lineno + 1;
if (real_lineno == 1 ||
(real_lineno % opt_num_interval) == 0) {
wprintw(view->win, "%.*d", view->digits, real_lineno);
} else {
waddnstr(view->win, spaces,
MIN(view->digits, STRING_SIZE(spaces)));
}
waddstr(view->win, ": ");
col_offset = view->digits + 2;
}
while (line && col_offset + col < view->width) {
int cols_max = view->width - col_offset - col;
char *text = line;
int cols;
if (*line == '\t') {
assert(sizeof(spaces) > TABSIZE);
line++;
text = spaces;
cols = opt_tab_size - (col % opt_tab_size);
} else {
line = strchr(line, '\t');
cols = line ? line - text : strlen(text);
}
waddnstr(view->win, text, MIN(cols, cols_max));
col += cols;
}
} else {
int col = 0, pos = 0;
for (; pos < linelen && col < view->width; pos++, col++)
if (line[pos] == '\t')
col += TABSIZE - (col % TABSIZE) - 1;
waddnstr(view->win, line, pos);
}
return TRUE;
}
static bool
pager_read(struct view *view, char *line)
{
/* Compress empty lines in the help view. */
if (view == VIEW(REQ_VIEW_HELP) &&
!*line &&
view->lines &&
!*((char *) view->line[view->lines - 1]))
return TRUE;
view->line[view->lines] = strdup(line);
if (!view->line[view->lines])
return FALSE;
view->lines++;
return TRUE;
}
static bool
pager_enter(struct view *view)
{
char *line = view->line[view->lineno];
if (get_line_type(line) == LINE_COMMIT) {
if (view == VIEW(REQ_VIEW_LOG))
open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT | OPEN_BACKGROUNDED);
else
open_view(view, REQ_VIEW_DIFF, OPEN_DEFAULT);
}
return TRUE;
}
static struct view_ops pager_ops = {
"line",
pager_draw,
pager_read,
pager_enter,
};
static struct ref **get_refs(char *id);
static bool
main_draw(struct view *view, unsigned int lineno)
{
char buf[DATE_COLS + 1];
struct commit *commit;
enum line_type type;
int col = 0;
size_t timelen;
if (view->offset + lineno >= view->lines)
return FALSE;
commit = view->line[view->offset + lineno];
if (!*commit->author)
return FALSE;
wmove(view->win, lineno, col);
if (view->offset + lineno == view->lineno) {
string_copy(view->ref, commit->id);
string_copy(ref_commit, view->ref);
type = LINE_CURSOR;
wattrset(view->win, get_line_attr(type));
wchgat(view->win, -1, 0, type, NULL);
} else {
type = LINE_MAIN_COMMIT;
wattrset(view->win, get_line_attr(LINE_MAIN_DATE));
}
timelen = strftime(buf, sizeof(buf), DATE_FORMAT, &commit->time);
waddnstr(view->win, buf, timelen);
waddstr(view->win, " ");
col += DATE_COLS;
wmove(view->win, lineno, col);
if (type != LINE_CURSOR)
wattrset(view->win, get_line_attr(LINE_MAIN_AUTHOR));
if (strlen(commit->author) > 19) {
waddnstr(view->win, commit->author, 18);
if (type != LINE_CURSOR)
wattrset(view->win, get_line_attr(LINE_MAIN_DELIM));
waddch(view->win, '~');
} else {
waddstr(view->win, commit->author);
}
col += 20;
if (type != LINE_CURSOR)
wattrset(view->win, A_NORMAL);
mvwaddch(view->win, lineno, col, ACS_LTEE);
wmove(view->win, lineno, col + 2);
col += 2;
if (commit->refs) {
size_t i = 0;
do {
if (type == LINE_CURSOR)
;
else if (commit->refs[i]->tag)
wattrset(view->win, get_line_attr(LINE_MAIN_TAG));
else
wattrset(view->win, get_line_attr(LINE_MAIN_REF));
waddstr(view->win, "[");
waddstr(view->win, commit->refs[i]->name);
waddstr(view->win, "]");
if (type != LINE_CURSOR)
wattrset(view->win, A_NORMAL);
waddstr(view->win, " ");
col += strlen(commit->refs[i]->name) + STRING_SIZE("[] ");
} while (commit->refs[i++]->next);
}
if (type != LINE_CURSOR)
wattrset(view->win, get_line_attr(type));
{
int titlelen = strlen(commit->title);
if (col + titlelen > view->width)
titlelen = view->width - col;
waddnstr(view->win, commit->title, titlelen);
}
return TRUE;
}
/* Reads git log --pretty=raw output and parses it into the commit struct. */
static bool
main_read(struct view *view, char *line)
{
enum line_type type = get_line_type(line);
struct commit *commit;
switch (type) {
case LINE_COMMIT:
commit = calloc(1, sizeof(struct commit));
if (!commit)
return FALSE;
line += STRING_SIZE("commit ");
view->line[view->lines++] = commit;
string_copy(commit->id, line);
commit->refs = get_refs(commit->id);
break;
case LINE_AUTHOR:
{
char *ident = line + STRING_SIZE("author ");
char *end = strchr(ident, '<');
if (end) {
for (; end > ident && isspace(end[-1]); end--) ;
*end = 0;
}
commit = view->line[view->lines - 1];
string_copy(commit->author, ident);
/* Parse epoch and timezone */
if (end) {
char *secs = strchr(end + 1, '>');
char *zone;
time_t time;
if (!secs || secs[1] != ' ')
break;
secs += 2;
time = (time_t) atol(secs);
zone = strchr(secs, ' ');
if (zone && strlen(zone) == STRING_SIZE(" +0700")) {
long tz;
zone++;
tz = ('0' - zone[1]) * 60 * 60 * 10;
tz += ('0' - zone[2]) * 60 * 60;
tz += ('0' - zone[3]) * 60;
tz += ('0' - zone[4]) * 60;
if (zone[0] == '-')
tz = -tz;
time -= tz;
}
gmtime_r(&time, &commit->time);
}
break;
}
default:
/* We should only ever end up here if there has already been a
* commit line, however, be safe. */
if (view->lines == 0)
break;
/* Fill in the commit title if it has not already been set. */
commit = view->line[view->lines - 1];
if (commit->title[0])
break;
/* Require titles to start with a non-space character at the
* offset used by git log. */
/* FIXME: More gracefull handling of titles; append "..." to
* shortened titles, etc. */
if (strncmp(line, " ", 4) ||
isspace(line[4]))
break;
string_copy(commit->title, line + 4);
}
return TRUE;
}
static bool
main_enter(struct view *view)
{
open_view(view, REQ_VIEW_DIFF, OPEN_SPLIT | OPEN_BACKGROUNDED);
return TRUE;
}
static struct view_ops main_ops = {
"commit",
main_draw,
main_read,
main_enter,
};
/**
* KEYS
* ----
* Below the default key bindings are shown.
**/
struct keymap {
int alias;
int request;
};
static struct keymap keymap[] = {
/**
* View switching
* ~~~~~~~~~~~~~~
* m::
* Switch to main view.
* d::
* Switch to diff view.
* l::
* Switch to log view.
* p::
* Switch to pager view.
* h::
* Show man page.
* Return::
* If on a commit line show the commit diff. Additionally, if in
* main or log view this will split the view. To open the commit
* diff in full size view either use 'd' or press Return twice.
* Tab::
* Switch to next view.
**/
{ 'm', REQ_VIEW_MAIN },
{ 'd', REQ_VIEW_DIFF },
{ 'l', REQ_VIEW_LOG },
{ 'p', REQ_VIEW_PAGER },
{ 'h', REQ_VIEW_HELP },
{ KEY_TAB, REQ_VIEW_NEXT },
{ KEY_RETURN, REQ_ENTER },
/**
* Cursor navigation
* ~~~~~~~~~~~~~~~~~
* Up::
* Move cursor one line up.
* Down::
* Move cursor one line down.
* k::
* Move cursor one line up and enter. When used in the main view
* this will always show the diff of the current commit in the
* split diff view.
* j::
* Move cursor one line down and enter.
* PgUp::
* Move cursor one page up.
* PgDown::
* Move cursor one page down.
* Home::
* Jump to first line.
* End::
* Jump to last line.
**/
{ KEY_UP, REQ_MOVE_UP },
{ KEY_DOWN, REQ_MOVE_DOWN },
{ 'k', REQ_MOVE_UP_ENTER },
{ 'j', REQ_MOVE_DOWN_ENTER },
{ KEY_HOME, REQ_MOVE_FIRST_LINE },
{ KEY_END, REQ_MOVE_LAST_LINE },
{ KEY_NPAGE, REQ_MOVE_PAGE_DOWN },
{ KEY_PPAGE, REQ_MOVE_PAGE_UP },
/**
* Scrolling
* ~~~~~~~~~
* Insert::
* Scroll view one line up.
* Delete::
* Scroll view one line down.
* w::
* Scroll view one page up.
* s::
* Scroll view one page down.
**/
{ KEY_IC, REQ_SCROLL_LINE_UP },
{ KEY_DC, REQ_SCROLL_LINE_DOWN },
{ 'w', REQ_SCROLL_PAGE_UP },
{ 's', REQ_SCROLL_PAGE_DOWN },
/**
* Misc
* ~~~~
* q::
* Quit
* r::
* Redraw screen.
* z::
* Stop all background loading. This can be useful if you use
* tig(1) in a repository with a long history without limiting
* the revision log.
* v::
* Show version.
* n::
* Toggle line numbers on/off.
* ':'::
* Open prompt. This allows you to specify what git command
* to run. Example:
*
* :log -p
**/
{ 'q', REQ_QUIT },
{ 'z', REQ_STOP_LOADING },
{ 'v', REQ_SHOW_VERSION },
{ 'r', REQ_SCREEN_REDRAW },
{ 'n', REQ_TOGGLE_LINE_NUMBERS },
{ ':', REQ_PROMPT },
/* wgetch() with nodelay() enabled returns ERR when there's no input. */
{ ERR, REQ_SCREEN_UPDATE },
/* Use the ncurses SIGWINCH handler. */
{ KEY_RESIZE, REQ_SCREEN_RESIZE },
};
static enum request
get_request(int key)
{
int i;
for (i = 0; i < ARRAY_SIZE(keymap); i++)
if (keymap[i].alias == key)
return keymap[i].request;
return (enum request) key;
}
/*
* Status management
*/
/* Whether or not the curses interface has been initialized. */
bool cursed = FALSE;
/* The status window is used for polling keystrokes. */
static WINDOW *status_win;
/* Update status and title window. */
static void
report(const char *msg, ...)
{
static bool empty = TRUE;
struct view *view = display[current_view];
if (!empty || *msg) {
va_list args;
va_start(args, msg);
werase(status_win);
wmove(status_win, 0, 0);
if (*msg) {
vwprintw(status_win, msg, args);
empty = FALSE;
} else {
empty = TRUE;
}
wrefresh(status_win);
va_end(args);
}
update_view_title(view);
/* Move the cursor to the right-most column of the cursor line.
*
* XXX: This could turn out to be a bit expensive, but it ensures that
* the cursor does not jump around. */
if (view->lines) {
wmove(view->win, view->lineno - view->offset, view->width - 1);
wrefresh(view->win);
}
}
/* Controls when nodelay should be in effect when polling user input. */
static void
set_nonblocking_input(bool loading)
{
static unsigned int loading_views;
if ((loading == FALSE && loading_views-- == 1) ||
(loading == TRUE && loading_views++ == 0))
nodelay(status_win, loading);
}
static void
init_display(void)
{
int x, y;
/* Initialize the curses library */
if (isatty(STDIN_FILENO)) {
cursed = !!initscr();
} else {
/* Leave stdin and stdout alone when acting as a pager. */
FILE *io = fopen("/dev/tty", "r+");
cursed = !!newterm(NULL, io, io);
}
if (!cursed)
die("Failed to initialize curses");
nonl(); /* Tell curses not to do NL->CR/NL on output */
cbreak(); /* Take input chars one at a time, no wait for \n */
noecho(); /* Don't echo input */
leaveok(stdscr, TRUE);
if (has_colors())
init_colors();
getmaxyx(stdscr, y, x);
status_win = newwin(1, 0, y - 1, 0);
if (!status_win)
die("Failed to create status window");
/* Enable keyboard mapping */
keypad(status_win, TRUE);
wbkgdset(status_win, get_line_attr(LINE_STATUS));
}
/*
* Repository references
*/
static struct ref *refs;
static size_t refs_size;
static struct ref **
get_refs(char *id)
{
struct ref **id_refs = NULL;
size_t id_refs_size = 0;
size_t i;
for (i = 0; i < refs_size; i++) {
struct ref **tmp;
if (strcmp(id, refs[i].id))
continue;
tmp = realloc(id_refs, (id_refs_size + 1) * sizeof(*id_refs));
if (!tmp) {
if (id_refs)
free(id_refs);
return NULL;
}
id_refs = tmp;
if (id_refs_size > 0)
id_refs[id_refs_size - 1]->next = 1;
id_refs[id_refs_size] = &refs[i];
/* XXX: The properties of the commit chains ensures that we can
* safely modify the shared ref. The repo references will
* always be similar for the same id. */
id_refs[id_refs_size]->next = 0;
id_refs_size++;
}
return id_refs;
}
static int
load_refs(void)
{
char *cmd_env = getenv("TIG_LS_REMOTE");
char *cmd = cmd_env && *cmd_env ? cmd_env : TIG_LS_REMOTE;
FILE *pipe = popen(cmd, "r");
char buffer[BUFSIZ];
char *line;
if (!pipe)
return ERR;
while ((line = fgets(buffer, sizeof(buffer), pipe))) {
char *name = strchr(line, '\t');
struct ref *ref;
int namelen;
bool tag = FALSE;
bool tag_commit = FALSE;
if (!name)
continue;
*name++ = 0;
namelen = strlen(name) - 1;
/* Commits referenced by tags has "^{}" appended. */
if (name[namelen - 1] == '}') {
while (namelen > 0 && name[namelen] != '^')
namelen--;
if (namelen > 0)
tag_commit = TRUE;
}
name[namelen] = 0;
if (!strncmp(name, "refs/tags/", STRING_SIZE("refs/tags/"))) {
if (!tag_commit)
continue;
name += STRING_SIZE("refs/tags/");
tag = TRUE;
} else if (!strncmp(name, "refs/heads/", STRING_SIZE("refs/heads/"))) {
name += STRING_SIZE("refs/heads/");
} else if (!strcmp(name, "HEAD")) {
continue;
}
refs = realloc(refs, sizeof(*refs) * (refs_size + 1));
if (!refs)
return ERR;
ref = &refs[refs_size++];
ref->tag = tag;
ref->name = strdup(name);
if (!ref->name)
return ERR;
string_copy(ref->id, line);
}
if (ferror(pipe))
return ERR;
pclose(pipe);
if (refs_size == 0)
die("Not a git repository");
return OK;
}
/*
* Main
*/
static void
quit(int sig)
{
/* XXX: Restore tty modes and let the OS cleanup the rest! */
if (cursed)
endwin();
exit(0);
}
static void
die(const char *err, ...)
{
va_list args;
endwin();
va_start(args, err);
fputs("tig: ", stderr);
vfprintf(stderr, err, args);
fputs("\n", stderr);
va_end(args);
exit(1);
}
int
main(int argc, char *argv[])
{
struct view *view;
enum request request;
size_t i;
signal(SIGINT, quit);
if (!parse_options(argc, argv))
return 0;
if (load_refs() == ERR)
die("Failed to load refs.");
for (i = 0; i < ARRAY_SIZE(views) && (view = &views[i]); i++)
view->cmd_env = getenv(view->cmd_env);
request = opt_request;
init_display();
while (view_driver(display[current_view], request)) {
int key;
int i;
foreach_view (view, i)
update_view(view);
/* Refresh, accept single keystroke of input */
key = wgetch(status_win);
request = get_request(key);
/* Some low-level request handling. This keeps access to
* status_win restricted. */
switch (request) {
case REQ_PROMPT:
report(":");
/* Temporarily switch to line-oriented and echoed
* input. */
nocbreak();
echo();
if (wgetnstr(status_win, opt_cmd + 4, sizeof(opt_cmd) - 4) == OK) {
memcpy(opt_cmd, "git ", 4);
opt_request = REQ_VIEW_PAGER;
} else {
request = ERR;
}
noecho();
cbreak();
break;
case REQ_SCREEN_RESIZE:
{
int height, width;
getmaxyx(stdscr, height, width);
/* Resize the status view and let the view driver take
* care of resizing the displayed views. */
wresize(status_win, 1, width);
mvwin(status_win, height - 1, 0);
wrefresh(status_win);
break;
}
default:
break;
}
}
quit(0);
return 0;
}
/**
* [[refspec]]
* Revision specification
* ----------------------
* This section describes various ways to specify what revisions to display
* or otherwise limit the view to. tig(1) does not itself parse the described
* revision options so refer to the relevant git man pages for futher
* information. Relevant man pages besides git-log(1) are git-diff(1) and
* git-rev-list(1).
*
* You can tune the interaction with git by making use of the options
* explained in this section. For example, by configuring the environment
* variables described in the <<view-commands, "View commands">> section.
*
* Limit by path name
* ~~~~~~~~~~~~~~~~~~
* If you are interested only in those revisions that made changes to a
* specific file (or even several files) list the files like this:
*
* $ tig log Makefile
*
* To avoid ambiguity with repository references such as tag name, be sure
* to separate file names from other git options using "\--". So if you
* have a file named 'master' it will clash with the reference named
* 'master', and thus you will have to use:
*
* $ tig log -- master
*
* NOTE: For the main view, avoiding ambiguity will in some cases require
* you to specify two "\--" options. The first will make tig(1) stop
* option processing and the latter will be passed to git log.
*
* Limit by date or number
* ~~~~~~~~~~~~~~~~~~~~~~~
* To speed up interaction with git, you can limit the amount of commits
* to show both for the log and main view. Either limit by date using
* e.g. `--since=1.month` or limit by the number of commits using `-n400`.
*
* If you are only interested in changed that happened between two dates
* you can use:
*
* $ tig -- --after=May.5th --before=2006-05-16.15:44
*
* NOTE: The dot (".") is used as a separator instead of a space to avoid
* having to quote the option value. If you prefer use `--after="May 5th"`
* instead of `--after="May 5th"`.
*
* Limiting by commit ranges
* ~~~~~~~~~~~~~~~~~~~~~~~~~
* Alternatively, commits can be limited to a specific range, such as
* "all commits between 'tag-1.0' and 'tag-2.0'". For example:
*
* $ tig log tag-1.0..tag-2.0
*
* This way of commit limiting makes it trivial to only browse the commits
* which haven't been pushed to a remote branch. Assuming 'origin' is your
* upstream remote branch, using:
*
* $ tig log origin..HEAD
*
* will list what will be pushed to the remote branch. Optionally, the ending
* 'HEAD' can be left out since it is implied.
*
* Limiting by reachability
* ~~~~~~~~~~~~~~~~~~~~~~~~
* Git interprets the range specifier "tag-1.0..tag-2.0" as
* "all commits reachable from 'tag-2.0' but not from 'tag-1.0'".
* Where reachability refers to what commits are ancestors (or part of the
* history) of the branch or tagged revision in question.
*
* If you prefer to specify which commit to preview in this way use the
* following:
*
* $ tig log tag-2.0 ^tag-1.0
*
* You can think of '^' as a negation operator. Using this alternate syntax,
* it is possible to further prune commits by specifying multiple branch
* cut offs.
*
* Combining revisions specification
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Revisions options can to some degree be combined, which makes it possible
* to say "show at most 20 commits from within the last month that changed
* files under the Documentation/ directory."
*
* $ tig -- --since=1.month -n20 -- Documentation/
*
* Examining all repository references
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* In some cases, it can be useful to query changes across all references
* in a repository. An example is to ask "did any line of development in
* this repository change a particular file within the last week". This
* can be accomplished using:
*
* $ tig -- --all --since=1.week -- Makefile
*
* BUGS
* ----
* Known bugs and problems:
*
* - If the screen width is very small the main view can draw
* outside the current view causing bad wrapping. Same goes
* for title and status windows.
*
* TODO
* ----
* Features that should be explored.
*
* - Searching.
*
* - Locale support.
*
* COPYRIGHT
* ---------
* Copyright (c) Jonas Fonseca <fonseca@diku.dk>, 2006
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* SEE ALSO
* --------
* [verse]
* link:http://www.kernel.org/pub/software/scm/git/docs/[git(7)],
* link:http://www.kernel.org/pub/software/scm/cogito/docs/[cogito(7)]
* gitk(1): git repository browser written using tcl/tk,
* qgit(1): git repository browser written using c++/Qt,
* gitview(1): git repository browser written using python/gtk.
**/
^ permalink raw reply
* [ANNOUNCE] tig - text-mode interface for git
From: Jonas Fonseca @ 2006-05-17 12:07 UTC (permalink / raw)
To: git
Hello,
I am pleased to announce tig, a simple git repository browser written
using ncurses. Basically, it just acts as a front-end for git-log and
git-show/git-diff. Additionally, you can also use it as a pager for git
commands.
Currently, it just provides a minimum support for looking through
changes. I hope to slowly extend it to also be usable as a front-end for
git-blame and for tree browsing. Also, it doesn't do any fancy revision
graph rendering, but I want to at least give it a shot at some point. :)
Following is the man page. I've attached the tig.c file if you want to
try it out. Compile it with:
$ cc -o tig tig.c -lncurses
More project files (including a Makefile) can be found at:
http://jonas.nitro.dk/tig/
Obligatory screenshots at:
http://jonas.nitro.dk/tig/screenshots/
---
NAME
tig - text-mode interface for git
SYNOPSIS
tig [options]
tig [options] [--] [git log options]
tig [options] log [git log options]
tig [options] diff [git diff options]
tig [options] show [git show options]
tig [options] < [git command output]
DESCRIPTION
Browse changes in a git repository. Additionally, tig(1) can also act
as a pager for output of various git commands.
When browsing repositories, tig(1) uses the underlying git commands
to present the user with various views, such as summarized commit log
and showing the commit with the log message, diffstat, and the diff.
Using tig(1) as a pager, it will display input from stdin and try to
colorize it.
OPTIONS
-l
Start up in log view using the internal log command.
-d
Start up in diff view using the internal diff command.
-n[INTERVAL], --line-number[=INTERVAL]
Prefix line numbers in log and diff view. Optionally, with
interval different than each line.
-t[NSPACES], --tab-size[=NSPACES]
Set the number of spaces tabs should be expanded to.
-v, --version
Show version and exit.
--
End of tig(1) options. Useful when specifying command options
for the main view. Example:
$ tig -- --since=1.month
log [git log options]
Open log view using the given git log options.
diff [git diff options]
Open diff view using the given git diff options.
show [git show options]
Open diff view using the given git show options.
[git log options]
tig(1) will stop the option parsing when the first command
line parameter not starting with "-" is encountered. All
options including this one will be passed to git log when
loading the main view. This makes it possible to say:
$ tig tag-1.0..HEAD
Pager mode
If stdin is a pipe, any log or diff options will be ignored and the
pager view will be opened loading data from stdin. The pager mode can
be used for colorizing output from various git commands.
Example on how to colorize the output of git-show(1):
$ git show | tig
Git command options
All git command options specified on the command line will be passed
to the given command and all will be shell quoted before they are
passed to the shell.
If you specify options for the main view, you should not use the
Note --pretty option as this option will be set automatically to the
format expected by the main view.
Example on how to open the log view and show both author and
committer information:
$ tig log --pretty=fuller
See the "Specifying revisions" section below for an introduction to
revision options supported by the git commands. For details on
specific git command options, refer to the man page of the command in
question.
ENVIRONMENT VARIABLES
Several options related to the interface with git can be configured
via environment options.
Repository references
Commits that are referenced by tags and branch heads will be marked
by the reference name surrounded by [ and ]:
2006-03-26 19:42 Petr Baudis | [cogito-0.17.1] Cogito 0.17.1
If you want to filter out certain directories under .git/refs/, say
tmp you can do it by setting the following variable:
$ TIG_LS_REMOTE="git ls-remote . | sed /\/tmp\//d" tig
Or set the variable permanently in your environment.
TIG_LS_REMOTE
Set command for retrieving all repository references. The
command should output data in the same format as
git-ls-remote(1).
View commands
It is possible to alter which commands are used for the different
views. If for example you prefer commits in the main view to be
sorted by date and only show 500 commits, use:
$ TIG_MAIN_CMD="git log --date-order -n500 --pretty=raw %s" tig
Or set the variable permanently in your environment.
Notice, how %s is used to specify the commit reference. There can be
a maximum of 5 %s ref specifications.
TIG_DIFF_CMD
The command used for the diff view. By default, git show is
used as a backend.
TIG_LOG_CMD
The command used for the log view. If you prefer to have both
author and committer shown in the log view be sure to pass
--pretty=fuller to git log.
TIG_MAIN_CMD
The command used for the main view. Note, you must always
specify the option: --pretty=raw since the main view parser
expects to read that format.
The viewer
tig(1) presents various views of a repository. Each view is based on
output from an external command, most often git log, git diff, or git
show.
The main view
Is the default view, and it shows a one line summary of each
commit in the chosen list of revisions. The summary includes
commit date, author, and the first line of the log message.
Additionally, any repository references, such as tags, will
be shown.
The log view
Presents a more rich view of the revision log showing the
whole log message and the diffstat.
The diff view
Shows either the diff of the current working tree, that is,
what has changed since the last commit, or the commit diff
complete with log message, diffstat and diff.
The pager view
Is used for displaying both input from stdin and output from
git commands entered in the internal prompt.
The help view
Displays the information from the tig(1) man page. For the
help view to work you need to have the tig(1) man page
installed.
KEYS
Below the default key bindings are shown.
View switching
m Switch to main view.
d Switch to diff view.
l Switch to log view.
p Switch to pager view.
h Show man page.
Return If on a commit line show the commit diff. Additionally, if in
main or log view this will split the view. To open the commit
diff in full size view either use d or press Return twice.
Tab Switch to next view.
Cursor navigation
Up Move cursor one line up.
Down Move cursor one line down.
k Move cursor one line up and enter. When used in the main view
this will always show the diff of the current commit in the
split diff view.
j Move cursor one line down and enter.
PgUp Move cursor one page up.
PgDown Move cursor one page down.
Home Jump to first line.
End Jump to last line.
Scrolling
Insert Scroll view one line up.
Delete Scroll view one line down.
w Scroll view one page up.
s Scroll view one page down.
Misc
q Quit
r Redraw screen.
z Stop all background loading. This can be useful if you use
tig(1) in a repository with a long history without limiting
the revision log.
v Show version.
n Toggle line numbers on/off.
: Open prompt. This allows you to specify what git command to
run. Example:
:log -p
Revision specification
This section describes various ways to specify what revisions to
display or otherwise limit the view to. tig(1) does not itself parse
the described revision options so refer to the relevant git man pages
for futher information. Relevant man pages besides git-log(1) are
git-diff(1) and git-rev-list(1).
You can tune the interaction with git by making use of the options
explained in this section. For example, by configuring the
environment variables described in the "View commands" section.
Limit by path name
If you are interested only in those revisions that made changes to a
specific file (or even several files) list the files like this:
$ tig log Makefile
To avoid ambiguity with repository references such as tag name, be
sure to separate file names from other git options using "--". So if
you have a file named master it will clash with the reference named
master, and thus you will have to use:
$ tig log -- master
For the main view, avoiding ambiguity will in some cases require
Note you to specify two "--" options. The first will make tig(1) stop
option processing and the latter will be passed to git log.
Limit by date or number
To speed up interaction with git, you can limit the amount of commits
to show both for the log and main view. Either limit by date using
e.g. --since=1.month or limit by the number of commits using -n400.
If you are only interested in changed that happened between two dates
you can use:
$ tig -- --after=May.5th --before=2006-05-16.15:44
The dot (".") is used as a separator instead of a space to avoid
Note having to quote the option value. If you prefer use --after="May
5th" instead of --after="May 5th".
Limiting by commit ranges
Alternatively, commits can be limited to a specific range, such as
"all commits between tag-1.0 and tag-2.0". For example:
$ tig log tag-1.0..tag-2.0
This way of commit limiting makes it trivial to only browse the
commits which haven't been pushed to a remote branch. Assuming origin
is your upstream remote branch, using:
$ tig log origin..HEAD
will list what will be pushed to the remote branch. Optionally, the
ending HEAD can be left out since it is implied.
Limiting by reachability
Git interprets the range specifier "tag-1.0..tag-2.0" as "all commits
reachable from tag-2.0 but not from tag-1.0". Where reachability
refers to what commits are ancestors (or part of the history) of the
branch or tagged revision in question.
If you prefer to specify which commit to preview in this way use the
following:
$ tig log tag-2.0 ^tag-1.0
You can think of ^ as a negation operator. Using this alternate
syntax, it is possible to further prune commits by specifying
multiple branch cut offs.
Combining revisions specification
Revisions options can to some degree be combined, which makes it
possible to say "show at most 20 commits from within the last month
that changed files under the Documentation/ directory."
$ tig -- --since=1.month -n20 -- Documentation/
Examining all repository references
In some cases, it can be useful to query changes across all
references in a repository. An example is to ask "did any line of
development in this repository change a particular file within the
last week". This can be accomplished using:
$ tig -- --all --since=1.week -- Makefile
BUGS
Known bugs and problems:
* If the screen width is very small the main view can draw outside
the current view causing bad wrapping. Same goes for title and
status windows.
TODO
Features that should be explored.
* Searching.
* Locale support.
COPYRIGHT
Copyright (c) Jonas Fonseca <fonseca@diku.dk>, 2006
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.
SEE ALSO
git(7),
cogito(7)
gitk(1): git repository browser written using tcl/tk,
gitview(1): git repository browser written using python/gtk.
--
Jonas Fonseca
^ permalink raw reply
* [RFD] Git glossary: 'branch' and 'head' description
From: Jakub Narebski @ 2006-05-17 11:37 UTC (permalink / raw)
To: git
In #git channel somebody asked about 'branches' and 'heads' and was referred
to the glossary. I had taken then a look at appropriate glossary entries.
In 'Documentation/glossary.txt' we have:
----
branch::
A non-cyclical graph of revisions, i.e. the complete history of
a particular revision, which is called the branch head. The
branch heads are stored in `$GIT_DIR/refs/heads/`.
head::
The top of a branch. It contains a ref to the corresponding
commit object.
head ref::
A ref pointing to a head. Often, this is abbreviated to "head".
Head refs are stored in `$GIT_DIR/refs/heads/`.
revision::
A particular state of files and directories which was stored in
the object database. It is referenced by a commit object.
----
It is just me or the glossary entry for `branch` is unnecessary
complicated?
Let's take a look at other definitions:
In software engineering, 'branch' is a separate line of development, which
among others allows development on a project to diverge in two directions,
such as a stable and a development version. (WikiPedia:Branch)
In the CVS team development environment, a separate line of development
where changes can be isolated. When a programmer changes files on
a branch, those changes do not appear on the main trunk
or other branches. (cvs.info)
So from the user's point of view, 'branch' is simply _named line of
development_. Refer to topic and tracking branches.
>From the point of view of commit, current branch (or rather branch
head/branch tip) is the place where we attach current development line
(current commit has current head as parent, and head is advanced to the
current commit), something akin to 'top' pointer for stack implemented as
linked list.
ONLY from the point of view of HISTORY, branch define "non-cyclical graph of
revisions, i.e. the complete history of a particular revision, which is
called the branch head." Merges somewhat obscure the branches (as history),
unless for example we assume that first parent is on the same branch, or
use note header to mark appropriate parent/commit. Also git does not record
where branch started... New branch commit logging proposal, and per-branch
configuration should help with these issues.
No concrete proposal for git glossary update...
--
Jakub Narebski
Warsaw, Poland
^ permalink raw reply
* Re: "git add $ignored_file" fail
From: Johannes Schindelin @ 2006-05-17 11:08 UTC (permalink / raw)
To: Santi; +Cc: git
In-Reply-To: <8aa486160605161542u704ccf03w@mail.gmail.com>
On Wed, 17 May 2006, Santi wrote:
> 2006/5/17, Linus Torvalds <torvalds@osdl.org>:
> >
> >
> > On Wed, 17 May 2006, Santi wrote:
> > >
> > > When you try to add ignored files with the git-add command it
> > > fails [...]
> > >
> > [...]
> >
> > So it's consistent that it overrides it also for a single filename case,
> > no?
> >
>
> It's consistent from an implementation point of view, but not from the
> (my?) user point of view.
So, you told git to ignore the file. And then you say "please add it". I
find it highly consistent that git does not do anything, because *you*
decided it should ignore it in the first place.
Ciao,
Dscho
^ permalink raw reply
* Re: [RFC 5/5] Support 'master@2 hours ago' syntax
From: Junio C Hamano @ 2006-05-17 11:07 UTC (permalink / raw)
To: Shawn Pearce; +Cc: git
In-Reply-To: <20060517095609.GF28529@spearce.org>
Shawn Pearce <spearce@spearce.org> writes:
> Extended sha1 expressions may now include date specifications
> which indicate a point in time within the local repository's
> history. If the ref indicated to the left of '@' has a log in
> $GIT_DIR/logs/<ref> then the value of the ref at the time indicated
> by the specification is obtained from the ref's log.
This does not allow '2006-05-17 00:00:00' as the timespec, and
the documentation carefully avoids giving that example, but I
think it is better to spell that limitation out.
> +* A suffix '@' followed by a date specification such as 'yesterday'
> + (24 hours ago) or '1 month 2 weeks 3 days 1 hour 1 second ago'
> + to specify the value of the ref at a prior point in time.
> + This suffix may only be used immediately following a ref name
> + and the ref must have an existing log ($GIT_DIR/logs/<ref>).
+ fprintf(stderr, "warning: Log %s only goes back to %s.\n",
+ logfile, show_rfc2822_date(date, tz));
+ return 0;
I am not sure about this part. If the oldest log entry was 3
hours ago, the second oldest 2 hours ago, we can tell during
that one hour period the ref was at that point. If the user
asked "ref as of four hours ago", and if the oldest log entry
had old SHA1 that is not 0{40} (because the log was not enabled
before that record), it might make more sense to give that back.
Also I wonder how much complexity would we suffer and how much
efficiency would we gain if we binary search the logdata (the
committer info is variable length, so you would need to resync
in each step).
^ permalink raw reply
* Re: Fwd: [OT] Re: Git via a proxy server?
From: Petr Vandrovec @ 2006-05-17 10:54 UTC (permalink / raw)
To: Sam Song; +Cc: Jan-Benedict Glaw, git
In-Reply-To: <20060517083845.GC23642@lug-owl.de>
Jan-Benedict Glaw wrote:
> On Tue, 2006-05-16 20:56:39 -0700, Sam Song <samlinuxkernel@yahoo.com> wrote:
>
>>Petr Vandrovec <petr@vmware.com> wrote:
>>
>>>Best to test this is to start 'socket 192.168.40.99
>>>80' from command line and
>>>then type these two lines above, plus one empty
>>>line. You should get back '200
>>>OK', empty line, and then you can start
>>>communicating using git protocol - if
>>>you can do that...
>>
>>I cannot run "socket" and "CONNECT" on Fedora Core 3.
>>It simply told me that no such command. How could I
>>do this task in my case?
>
>
> Well, install some package to have `socket' available? Debian calls
> the packet `socket', too, so I guess Fedora may have something
> similar.
Surprisingly they do not... You should be able to replace 'socket' with
'netcat' - and I believe that netcat/nc package is available for Fedora. For
this purpose they have same command line & behavior.
Petr
^ permalink raw reply
* Re: [RFC 3/5] Convert update-ref to use ref_lock API.
From: Junio C Hamano @ 2006-05-17 10:49 UTC (permalink / raw)
To: Shawn Pearce; +Cc: git
In-Reply-To: <20060517095519.GD28529@spearce.org>
Shawn Pearce <spearce@spearce.org> writes:
> -'git-update-ref' <ref> <newvalue> [<oldvalue>]
> +'git-update-ref' <ref> <newvalue> [<oldvalue>] [-m <reason>]
Is it just me who feels "cmd [-m <reason>] <ref> <new> [<old>]" would
look more natural?
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox