* Re: Git 1.3.2 on Solaris
From: Stefan Pfetzing @ 2006-05-17 9:06 UTC (permalink / raw)
To: Git Mailing List
In-Reply-To: <7vpsidhx79.fsf@assigned-by-dhcp.cox.net>
Hi Junio,
2006/5/17, Junio C Hamano <junkio@cox.net>:
> "Stefan Pfetzing" <stefan.pfetzing@gmail.com> writes:
>
> > 1. fix every single shellscript automatically during the build phase
> > 2. setup a dir which contains symlinks to the "right" binaries and
> > put that dir into PATH.
>
> You forgot 3.
>
> 3. rewrite scripts so that they would require only POSIX;
> for ones that do need GNU extended coreutils to do in
> shell, find other ways, perhaps rewriting the stuff in C.
Yes, thats right, but this can only be a long term goal, because I guess
this will take significantly longer. - even "tr" and "diff" behave different
on Solaris.
> I am not looking forward to do the g- prefix in the main
> Makefile. The approach to have symlink forest under gitexecdir
> (<Pine.LNX.4.64.0605162047380.10823@g5.osdl.org> by Linus) is
> more palatable, and I am not opposed to host a script to do so
> under contrib/notgnu perhaps.
Hm, gitexecdir is also the path where git is installed, right? So if I'd
install git with pkgsrc it will be /usr/pkg/bin, right? - If so,
putting symlinks
there _will_ break pkgsrc.
bye
Stefan
--
http://www.dreamind.de/
Oroborus and Debian GNU/Linux Developer.
^ permalink raw reply
* Re: Git 1.3.2 on Solaris
From: Junio C Hamano @ 2006-05-17 9:22 UTC (permalink / raw)
To: Stefan Pfetzing; +Cc: git
In-Reply-To: <f3d7535d0605170206y76e24f25w305a688d32f4a0a1@mail.gmail.com>
"Stefan Pfetzing" <stefan.pfetzing@gmail.com> writes:
> Hm, gitexecdir is also the path where git is installed, right? So if I'd
> install git with pkgsrc it will be /usr/pkg/bin, right? - If so,
> putting symlinks
> there _will_ break pkgsrc.
If you look at our Makefile, you will see bindir does not have
to be gitexecdir. The suggestion by Linus is that you set
bindir to /usr/local/bin or whereever your distribution's
packaging scheme wants the locally installed software to be that
is on user's PATH, and gitexecdir to /usr/local/libexec/git
(again, whereever), _and_ have:
ln -s /usr/bin/gtr /usr/local/libexec/git/tr
ln -s /usr/bin/gxargs /usr/local/libexec/git/xargs
...
Then:
(1) git and gitk are available in /usr/local/bin;
(2) while git and gitk runs, /usr/local/libexec/git will
be prepended to the PATH, so when they want xargs,
they will get gxargs;
(3) but your users will _not_ have /usr/local/libexec/git
on their PATH, so when they type xargs they will get
the one that barfs on -0 option.
and train your users and user's scripts to use the officially
sanctioned way to refer to git subprograms. From interactive
sessions, say "git foo", not "git-foo". If your script _really_
cares about extra exec git wrapper does, use "git --exec-path"
upfront in the script to obtain correct gitexecpath, export
GIT_EXEC_PATH environment variable with that value, and prepend
it to PATH so that it can find "git-foo" executable (you would
probably need to do both, so that git-foo can find git-bar and
its friends).
^ permalink raw reply
* git-add + git-reset --hard = Arrrggh!
From: Shawn Pearce @ 2006-05-17 9:45 UTC (permalink / raw)
To: git
After spending an hour writing and testing a new test case for GIT
I do the foolish:
$ git add t/t1400-update-ref.sh
# Hmm, maybe I should amend this into the prior commit.
$ git format-patch -o .. next
$ git reset --hard
$ git update-ref HEAD~1
# Uhhohh...
$ ls t/t1400-update-ref.sh
All I can say is I'm very happy that update-index does a lot more
than just update the index. I was easily able to find the deleted
test by finding the most recently modified object in my .git/objects
directory and pulling it back out with git cat-file. :-)
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. :-)
Yes, I know that git reset --hard was brutal and yes, I didn't
really need to use git-update-ref when git-reset would have also
done the job for me. Arrgh. Its early and I wasn't thinking.
--
Shawn.
^ permalink raw reply
* [RFC 0/5] Log history of a ref
From: Shawn Pearce @ 2006-05-17 9:54 UTC (permalink / raw)
To: git
The following 5 [RFC] patches from me are all related to logging
changes made to a ref. These patches contain the same basic idea
as the two patches I floated earlier this week. Log files reside
in .git/logs<ref> and logging is enabled either by creating the
log file or by setting 'core.logAllRefUpdates' to true.
Summary is:
* [RFC 1/5] Remove unnecessary local in get_ref_sha1.
A minor code cleanup.
* [RFC 2/5] Improve abstraction of ref lock/write.
A major reorg of the write_ref_sha1 APIs. This reorg allows
all internal code to use the same logic for updating any ref,
which makes it much easier to hook logging in.
* [RFC 3/5] Convert update-ref to use ref_lock API.
Modify update-ref to use the reorg'd write_ref_sha1 API.
* [RFC 4/5] Log ref updates to logs/refs/<ref>
Add logging of refs. This is the pretty much my latest
logging patch but cleaned up and built on the above.
* [RFC 5/5] Support 'master@2 hours ago' syntax
Extend the SHA1 syntax to search the log for the SHA1 which
was valid at the given point in time.
I still need to fix the receive-pack code to log ref changes prior
to running the update hook. I'll probably look at that later this
week. I also need to edit the ~20 sites which call 'git-update-ref'
to make use of the new "-m <reason>" flag.
--
Shawn.
^ permalink raw reply
* [PATCH] builtin-grep: workaround for non GNU grep.
From: Junio C Hamano @ 2006-05-17 9:54 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <7vejythvkr.fsf@assigned-by-dhcp.cox.net>
Some implementations do not know what to do with -H; define
NO_H_OPTION_IN_GREP when you build git if your grep lacks -H.
Most of the time, it can be worked around by prepending
/dev/null to the argument list, but that causes -L and -c to
slightly misbehave (they both expose /dev/null is given), so
when these options are given, do not run external grep that does
not understand -H.
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
Junio C Hamano <junkio@cox.net> writes:
> But I think this approach breaks -L; I do not think Solaris
> supports -L, so it does not matter there, but on platforms that
> knows how to do -L it does.
So this is an updated version. I am not proud of the handling
of the new Makefile variable, although I like the C code that
does not need #ifdef thanks to it.
Makefile | 11 +++++++++++
builtin-grep.c | 22 +++++++++++++++++++---
2 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/Makefile b/Makefile
index 9ba608c..c67108d 100644
--- a/Makefile
+++ b/Makefile
@@ -46,6 +46,8 @@ # Patrick Mauritz).
#
# Define NO_MMAP if you want to avoid mmap.
#
+# Define NO_H_OPTION_IN_GREP if your grep does not understand -H.
+#
# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
#
# Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
@@ -444,6 +446,12 @@ ifdef NO_ACCURATE_DIFF
ALL_CFLAGS += -DNO_ACCURATE_DIFF
endif
+ifdef NO_H_OPTION_IN_GREP
+ NO_H_OPTION_IN_GREP=1
+else
+ NO_H_OPTION_IN_GREP=0
+endif
+
# Shell quote (do not use $(call) to accomodate ancient setups);
SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER))
@@ -526,6 +534,9 @@ git$X git.spec \
%.o: %.S
$(CC) -o $*.o -c $(ALL_CFLAGS) $<
+builtin-grep.o: builtin-grep.c
+ $(CC) -o $*.o -c $(ALL_CFLAGS) -DNO_H_OPTION_IN_GREP=$(NO_H_OPTION_IN_GREP) $<
+
exec_cmd.o: exec_cmd.c
$(CC) -o $*.o -c $(ALL_CFLAGS) '-DGIT_EXEC_PATH="$(gitexecdir_SQ)"' $<
diff --git a/builtin-grep.c b/builtin-grep.c
index 66111de..36512d8 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -453,7 +453,6 @@ static int external_grep(struct grep_opt
len = nr = 0;
push_arg("grep");
- push_arg("-H");
if (opt->fixed)
push_arg("-F");
if (opt->linenum)
@@ -503,7 +502,13 @@ static int external_grep(struct grep_opt
push_arg("-e");
push_arg(p->pattern);
}
- push_arg("--");
+
+ if (NO_H_OPTION_IN_GREP)
+ push_arg("/dev/null");
+ else {
+ push_arg("-H");
+ push_arg("--");
+ }
hit = 0;
argc = nr;
@@ -535,8 +540,19 @@ #ifdef __unix__
* Use the external "grep" command for the case where
* we grep through the checked-out files. It tends to
* be a lot more optimized
+ *
+ * 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;
--
1.3.3.g8a24
^ permalink raw reply related
* [RFC 1/5] Remove unnecessary local in get_ref_sha1.
From: Shawn Pearce @ 2006-05-17 9:54 UTC (permalink / raw)
To: git
Remove unnecessary local in get_ref_sha1.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
refs.c | 5 +----
1 files changed, 1 insertions(+), 4 deletions(-)
339dddea51656af46f6c8a2fba7f50d96ae2a434
diff --git a/refs.c b/refs.c
index 6c91ae6..0f3491f 100644
--- a/refs.c
+++ b/refs.c
@@ -220,12 +220,9 @@ static char *ref_lock_file_name(const ch
int get_ref_sha1(const char *ref, unsigned char *sha1)
{
- const char *filename;
-
if (check_ref_format(ref))
return -1;
- filename = git_path("refs/%s", ref);
- return read_ref(filename, sha1);
+ return read_ref(git_path("refs/%s", ref), sha1);
}
static int lock_ref_file(const char *filename, const char *lock_filename,
--
1.3.2.g7278
^ permalink raw reply related
* [RFC 2/5] Improve abstraction of ref lock/write.
From: Shawn Pearce @ 2006-05-17 9:55 UTC (permalink / raw)
To: git
Created 'struct ref_lock' to contain the data necessary to perform
a ref update. This change improves writing a ref as the file names
are generated only once (rather than twice) and supports following
symrefs (up to the maximum depth). Further the ref_lock structure
provides room to extend the update API with ref logging.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
fetch.c | 32 +++++----
refs.c | 236 ++++++++++++++++++++++++++++++---------------------------------
refs.h | 24 ++++--
3 files changed, 144 insertions(+), 148 deletions(-)
d38945e0c759f8739da4c24485578a8193d54c59
diff --git a/fetch.c b/fetch.c
index 73bde07..8bdaacb 100644
--- a/fetch.c
+++ b/fetch.c
@@ -204,14 +204,14 @@ static int mark_complete(const char *pat
int pull(char *target)
{
+ struct ref_lock *lock;
unsigned char sha1[20];
- int fd = -1;
save_commit_buffer = 0;
track_object_refs = 0;
- if (write_ref && current_ref) {
- fd = lock_ref_sha1(write_ref, current_ref);
- if (fd < 0)
+ if (write_ref) {
+ lock = lock_ref_sha1(write_ref, current_ref, 1);
+ if (!lock)
return -1;
}
@@ -219,20 +219,22 @@ int pull(char *target)
for_each_ref(mark_complete);
}
- if (interpret_target(target, sha1))
- return error("Could not interpret %s as something to pull",
- target);
- if (process(lookup_unknown_object(sha1)))
+ if (interpret_target(target, sha1)) {
+ error("Could not interpret %s as something to pull", target);
+ unlock_ref(lock);
+ return -1;
+ }
+ if (process(lookup_unknown_object(sha1))) {
+ unlock_ref(lock);
return -1;
- if (loop())
+ }
+ if (loop()) {
+ unlock_ref(lock);
return -1;
-
+ }
+
if (write_ref) {
- if (current_ref) {
- write_ref_sha1(write_ref, fd, sha1);
- } else {
- write_ref_sha1_unlocked(write_ref, sha1);
- }
+ return write_ref_sha1(lock, sha1, "git fetch");
}
return 0;
}
diff --git a/refs.c b/refs.c
index 0f3491f..91c8c44 100644
--- a/refs.c
+++ b/refs.c
@@ -198,26 +198,6 @@ int for_each_remote_ref(int (*fn)(const
return do_for_each_ref("refs/remotes", fn, 13);
}
-static char *ref_file_name(const char *ref)
-{
- char *base = get_refs_directory();
- int baselen = strlen(base);
- int reflen = strlen(ref);
- char *ret = xmalloc(baselen + 2 + reflen);
- sprintf(ret, "%s/%s", base, ref);
- return ret;
-}
-
-static char *ref_lock_file_name(const char *ref)
-{
- char *base = get_refs_directory();
- int baselen = strlen(base);
- int reflen = strlen(ref);
- char *ret = xmalloc(baselen + 7 + reflen);
- sprintf(ret, "%s/%s.lock", base, ref);
- return ret;
-}
-
int get_ref_sha1(const char *ref, unsigned char *sha1)
{
if (check_ref_format(ref))
@@ -225,94 +205,6 @@ int get_ref_sha1(const char *ref, unsign
return read_ref(git_path("refs/%s", ref), sha1);
}
-static int lock_ref_file(const char *filename, const char *lock_filename,
- const unsigned char *old_sha1)
-{
- int fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
- unsigned char current_sha1[20];
- int retval;
- if (fd < 0) {
- return error("Couldn't open lock file for %s: %s",
- filename, strerror(errno));
- }
- retval = read_ref(filename, current_sha1);
- if (old_sha1) {
- if (retval) {
- close(fd);
- unlink(lock_filename);
- return error("Could not read the current value of %s",
- filename);
- }
- if (memcmp(current_sha1, old_sha1, 20)) {
- close(fd);
- unlink(lock_filename);
- error("The current value of %s is %s",
- filename, sha1_to_hex(current_sha1));
- return error("Expected %s",
- sha1_to_hex(old_sha1));
- }
- } else {
- if (!retval) {
- close(fd);
- unlink(lock_filename);
- return error("Unexpectedly found a value of %s for %s",
- sha1_to_hex(current_sha1), filename);
- }
- }
- return fd;
-}
-
-int lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
-{
- char *filename;
- char *lock_filename;
- int retval;
- if (check_ref_format(ref))
- return -1;
- filename = ref_file_name(ref);
- lock_filename = ref_lock_file_name(ref);
- retval = lock_ref_file(filename, lock_filename, old_sha1);
- free(filename);
- free(lock_filename);
- return retval;
-}
-
-static int write_ref_file(const char *filename,
- const char *lock_filename, int fd,
- const unsigned char *sha1)
-{
- char *hex = sha1_to_hex(sha1);
- char term = '\n';
- if (write(fd, hex, 40) < 40 ||
- write(fd, &term, 1) < 1) {
- error("Couldn't write %s", filename);
- close(fd);
- return -1;
- }
- close(fd);
- rename(lock_filename, filename);
- return 0;
-}
-
-int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1)
-{
- char *filename;
- char *lock_filename;
- int retval;
- if (fd < 0)
- return -1;
- if (check_ref_format(ref))
- return -1;
- filename = ref_file_name(ref);
- lock_filename = ref_lock_file_name(ref);
- if (safe_create_leading_directories(filename))
- die("unable to create leading directory for %s", filename);
- retval = write_ref_file(filename, lock_filename, fd, sha1);
- free(filename);
- free(lock_filename);
- return retval;
-}
-
/*
* Make sure "ref" is something reasonable to have under ".git/refs/";
* We do not like it if:
@@ -365,25 +257,119 @@ int check_ref_format(const char *ref)
}
}
-int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1)
+static struct ref_lock* verify_lock(struct ref_lock *lock,
+ const unsigned char *old_sha1, int mustexist)
+{
+ char buf[40];
+ int nr, fd = open(lock->ref_file, O_RDONLY);
+ if (fd < 0 && (mustexist || errno != ENOENT)) {
+ error("Can't verify ref %s", lock->ref_file);
+ unlock_ref(lock);
+ return NULL;
+ }
+ nr = read(fd, buf, 40);
+ close(fd);
+ if (nr != 40 || get_sha1_hex(buf, lock->old_sha1) < 0) {
+ error("Can't verify ref %s", lock->ref_file);
+ unlock_ref(lock);
+ return NULL;
+ }
+ if (memcmp(lock->old_sha1, old_sha1, 20)) {
+ error("Ref %s is at %s but expected %s", lock->ref_file,
+ sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
+ unlock_ref(lock);
+ return NULL;
+ }
+ return lock;
+}
+
+static struct ref_lock* lock_ref_sha1_basic(const char *path,
+ int plen,
+ const unsigned char *old_sha1, int mustexist)
+{
+ struct ref_lock *lock;
+
+ lock = xcalloc(1, sizeof(struct ref_lock));
+ lock->lock_fd = -1;
+
+ plen = strlen(path) - plen;
+ path = resolve_ref(path, lock->old_sha1, mustexist);
+ if (!path) {
+ error("Can't read ref %s", path);
+ unlock_ref(lock);
+ return NULL;
+ }
+
+ lock->ref_file = strdup(path);
+ lock->lock_file = strdup(mkpath("%s.lock", lock->ref_file));
+
+ if (safe_create_leading_directories(lock->lock_file))
+ die("unable to create directory for %s", lock->lock_file);
+ lock->lock_fd = open(lock->lock_file,
+ O_WRONLY | O_CREAT | O_EXCL, 0666);
+ if (lock->lock_fd < 0) {
+ error("Couldn't open lock file %s: %s",
+ lock->lock_file, strerror(errno));
+ unlock_ref(lock);
+ return NULL;
+ }
+
+ return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
+}
+
+struct ref_lock* lock_ref_sha1(const char *ref,
+ const unsigned char *old_sha1, int mustexist)
{
- char *filename;
- char *lock_filename;
- int fd;
- int retval;
if (check_ref_format(ref))
+ return NULL;
+ return lock_ref_sha1_basic(git_path("refs/%s", ref),
+ strlen(ref), old_sha1, mustexist);
+}
+
+struct ref_lock* lock_any_ref_for_update(const char *ref,
+ const unsigned char *old_sha1, int mustexist)
+{
+ return lock_ref_sha1_basic(git_path("%s", ref),
+ strlen(ref), old_sha1, mustexist);
+}
+
+void unlock_ref (struct ref_lock *lock)
+{
+ if (lock->lock_fd >= 0) {
+ close(lock->lock_fd);
+ unlink(lock->lock_file);
+ }
+ if (lock->ref_file)
+ free(lock->ref_file);
+ if (lock->lock_file)
+ free(lock->lock_file);
+ free(lock);
+}
+
+int write_ref_sha1(struct ref_lock *lock,
+ const unsigned char *sha1, const char *logmsg)
+{
+ static char term = '\n';
+
+ if (!lock)
return -1;
- filename = ref_file_name(ref);
- lock_filename = ref_lock_file_name(ref);
- if (safe_create_leading_directories(filename))
- die("unable to create leading directory for %s", filename);
- fd = open(lock_filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
- if (fd < 0) {
- error("Writing %s", lock_filename);
- perror("Open");
+ if (!memcmp(lock->old_sha1, sha1, 20)) {
+ unlock_ref(lock);
+ return 0;
}
- retval = write_ref_file(filename, lock_filename, fd, sha1);
- free(filename);
- free(lock_filename);
- return retval;
+ if (write(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
+ write(lock->lock_fd, &term, 1) != 1
+ || close(lock->lock_fd) < 0) {
+ error("Couldn't write %s", lock->lock_file);
+ unlock_ref(lock);
+ return -1;
+ }
+ if (rename(lock->lock_file, lock->ref_file) < 0) {
+ error("Couldn't set %s", lock->ref_file);
+ unlock_ref(lock);
+ return -1;
+ }
+ lock->lock_fd = -1;
+ unlock_ref(lock);
+ return 0;
}
diff --git a/refs.h b/refs.h
index fa816c1..b7e9df2 100644
--- a/refs.h
+++ b/refs.h
@@ -1,6 +1,13 @@
#ifndef REFS_H
#define REFS_H
+struct ref_lock {
+ char *ref_file;
+ char *lock_file;
+ unsigned char old_sha1[20];
+ int lock_fd;
+};
+
/*
* Calls the specified function for each ref file until it returns nonzero,
* and returns the value
@@ -14,16 +21,17 @@ extern int for_each_remote_ref(int (*fn)
/** Reads the refs file specified into sha1 **/
extern int get_ref_sha1(const char *ref, unsigned char *sha1);
-/** Locks ref and returns the fd to give to write_ref_sha1() if the ref
- * has the given value currently; otherwise, returns -1.
- **/
-extern int lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
+/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
+extern struct ref_lock* lock_ref_sha1(const char *ref, const unsigned char *old_sha1, int mustexist);
+
+/** Locks any ref (for 'HEAD' type refs). */
+extern struct ref_lock* lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int mustexist);
-/** Writes sha1 into the refs file specified, locked with the given fd. **/
-extern int write_ref_sha1(const char *ref, int fd, const unsigned char *sha1);
+/** Release any lock taken but not written. **/
+extern void unlock_ref (struct ref_lock *lock);
-/** Writes sha1 into the refs file specified. **/
-extern int write_ref_sha1_unlocked(const char *ref, const unsigned char *sha1);
+/** Writes sha1 into the ref specified by the lock. **/
+extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
/** Returns 0 if target has the right format for a ref. **/
extern int check_ref_format(const char *target);
--
1.3.2.g7278
^ permalink raw reply related
* [RFC 3/5] Convert update-ref to use ref_lock API.
From: Shawn Pearce @ 2006-05-17 9:55 UTC (permalink / raw)
To: git
This conversion also adds the '-m' switch to update-ref allowing
the caller to record why the ref is changing. At present this is
merely copied down into the ref_lock API.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
Documentation/git-update-ref.txt | 2 -
update-ref.c | 103 ++++++++++++++------------------------
2 files changed, 38 insertions(+), 67 deletions(-)
4f017fce63b3cb56065731967a9684cb36ddd12e
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index 475237f..f0e710a 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -7,7 +7,7 @@ git-update-ref - update the object name
SYNOPSIS
--------
-'git-update-ref' <ref> <newvalue> [<oldvalue>]
+'git-update-ref' <ref> <newvalue> [<oldvalue>] [-m <reason>]
DESCRIPTION
-----------
diff --git a/update-ref.c b/update-ref.c
index fd48742..a1e6bb9 100644
--- a/update-ref.c
+++ b/update-ref.c
@@ -1,85 +1,56 @@
#include "cache.h"
#include "refs.h"
-static const char git_update_ref_usage[] = "git-update-ref <refname> <value> [<oldval>]";
-
-static int re_verify(const char *path, unsigned char *oldsha1, unsigned char *currsha1)
-{
- char buf[40];
- int fd = open(path, O_RDONLY), nr;
- if (fd < 0)
- return -1;
- nr = read(fd, buf, 40);
- close(fd);
- if (nr != 40 || get_sha1_hex(buf, currsha1) < 0)
- return -1;
- return memcmp(oldsha1, currsha1, 20) ? -1 : 0;
-}
+static const char git_update_ref_usage[] =
+"git-update-ref <refname> <value> [<oldval>] [-m <reason>]";
int main(int argc, char **argv)
{
- char *hex;
- const char *refname, *value, *oldval, *path;
- char *lockpath;
- unsigned char sha1[20], oldsha1[20], currsha1[20];
- int fd, written;
+ const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
+ struct ref_lock *lock;
+ unsigned char sha1[20], oldsha1[20];
+ int i;
setup_git_directory();
git_config(git_default_config);
- if (argc < 3 || argc > 4)
+
+ for (i = 1; i < argc; i++) {
+ if (!strcmp("-m", argv[i])) {
+ if (i+1 >= argc)
+ usage(git_update_ref_usage);
+ msg = argv[++i];
+ if (!*msg)
+ die("Refusing to perform update with empty message.");
+ if (strchr(msg, '\n'))
+ die("Refusing to perform update with \\n in message.");
+ continue;
+ }
+ if (!refname) {
+ refname = argv[i];
+ continue;
+ }
+ if (!value) {
+ value = argv[i];
+ continue;
+ }
+ if (!oldval) {
+ oldval = argv[i];
+ continue;
+ }
+ }
+ if (!refname || !value)
usage(git_update_ref_usage);
- refname = argv[1];
- value = argv[2];
- oldval = argv[3];
if (get_sha1(value, sha1))
die("%s: not a valid SHA1", value);
memset(oldsha1, 0, 20);
if (oldval && get_sha1(oldval, oldsha1))
die("%s: not a valid old SHA1", oldval);
- path = resolve_ref(git_path("%s", refname), currsha1, !!oldval);
- if (!path)
- die("No such ref: %s", refname);
-
- if (oldval) {
- if (memcmp(currsha1, oldsha1, 20))
- die("Ref %s is at %s but expected %s", refname, sha1_to_hex(currsha1), sha1_to_hex(oldsha1));
- /* Nothing to do? */
- if (!memcmp(oldsha1, sha1, 20))
- exit(0);
- }
- path = strdup(path);
- lockpath = mkpath("%s.lock", path);
- if (safe_create_leading_directories(lockpath) < 0)
- die("Unable to create all of %s", lockpath);
-
- fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
- if (fd < 0)
- die("Unable to create %s", lockpath);
- hex = sha1_to_hex(sha1);
- hex[40] = '\n';
- written = write(fd, hex, 41);
- close(fd);
- if (written != 41) {
- unlink(lockpath);
- die("Unable to write to %s", lockpath);
- }
-
- /*
- * Re-read the ref after getting the lock to verify
- */
- if (oldval && re_verify(path, oldsha1, currsha1) < 0) {
- unlink(lockpath);
- die("Ref lock failed");
- }
-
- /*
- * Finally, replace the old ref with the new one
- */
- if (rename(lockpath, path) < 0) {
- unlink(lockpath);
- die("Unable to create %s", path);
- }
+ lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, 0);
+ if (!lock)
+ return 1;
+ if (write_ref_sha1(lock, sha1, msg) < 0)
+ return 1;
return 0;
}
--
1.3.2.g7278
^ permalink raw reply related
* [RFC 4/5] Log ref updates to logs/refs/<ref>
From: Shawn Pearce @ 2006-05-17 9:55 UTC (permalink / raw)
To: git
If config parameter core.logAllRefUpdates is true or the log
file already exists then append a line to ".git/logs/refs/<ref>"
whenever git-update-ref <ref> is executed. Each log line contains
the following information:
oldsha1 <SP> newsha1 <SP> committer <LF>
where committer is the current user, date, time and timezone in
the standard GIT ident format. If the caller is unable to append
to the log file then git-update-ref will fail without updating <ref>.
An optional message may be included in the log line with the -m flag.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
Documentation/config.txt | 8 +++
Documentation/git-update-ref.txt | 26 +++++++++
cache.h | 1
config.c | 5 ++
environment.c | 1
refs.c | 56 +++++++++++++++++++
refs.h | 1
t/t1400-update-ref.sh | 112 ++++++++++++++++++++++++++++++++++++++
8 files changed, 210 insertions(+), 0 deletions(-)
create mode 100644 t/t1400-update-ref.sh
1b91f255386d1120bdcb434603993156c9fdde71
diff --git a/Documentation/config.txt b/Documentation/config.txt
index d1a4bec..e178ee2 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -70,6 +70,14 @@ core.preferSymlinkRefs::
This is sometimes needed to work with old scripts that
expect HEAD to be a symbolic link.
+core.logAllRefUpdates::
+ If true, `git-update-ref` will append a line to
+ "$GIT_DIR/logs/<ref>" listing the new SHA1 and the date/time
+ of the update. If the file does not exist it will be
+ created automatically. This information can be used to
+ determine what commit was the tip of a branch "2 days ago".
+ This value is false by default (no logging).
+
core.repositoryFormatVersion::
Internal variable identifying the repository format and layout
version.
diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt
index f0e710a..dfbd886 100644
--- a/Documentation/git-update-ref.txt
+++ b/Documentation/git-update-ref.txt
@@ -49,6 +49,32 @@ for reading but not for writing (so we'l
ref symlink to some other tree, if you have copied a whole
archive by creating a symlink tree).
+Logging Updates
+---------------
+If config parameter "core.logAllRefUpdates" is true or the file
+"$GIT_DIR/logs/<ref>" exists then `git-update-ref` will append
+a line to the log file "$GIT_DIR/logs/<ref>" (dereferencing all
+symbolic refs before creating the log name) describing the change
+in ref value. Log lines are formatted as:
+
+ . oldsha1 SP newsha1 SP committer LF
++
+Where "oldsha1" is the 40 character hexadecimal value previously
+stored in <ref>, "newsha1" is the 40 character hexadecimal value of
+<newvalue> and "committer" is the committer's name, email address
+and date in the standard GIT committer ident format.
+
+Optionally with -m:
+
+ . oldsha1 SP newsha1 SP committer TAB message LF
++
+Where all fields are as described above and "message" is the
+value supplied to the -m option.
+
+An update will fail (without changing <ref>) if the current user is
+unable to create a new log file, append to the existing log file
+or does not have committer information available.
+
Author
------
Written by Linus Torvalds <torvalds@osdl.org>.
diff --git a/cache.h b/cache.h
index b1300cd..82adbba 100644
--- a/cache.h
+++ b/cache.h
@@ -171,6 +171,7 @@ extern void rollback_index_file(struct c
extern int trust_executable_bit;
extern int assume_unchanged;
extern int prefer_symlink_refs;
+extern int log_all_ref_updates;
extern int warn_ambiguous_refs;
extern int diff_rename_limit_default;
extern int shared_repository;
diff --git a/config.c b/config.c
index 0248c6d..2ae6153 100644
--- a/config.c
+++ b/config.c
@@ -269,6 +269,11 @@ int git_default_config(const char *var,
return 0;
}
+ if (!strcmp(var, "core.logallrefupdates")) {
+ log_all_ref_updates = git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "core.warnambiguousrefs")) {
warn_ambiguous_refs = git_config_bool(var, value);
return 0;
diff --git a/environment.c b/environment.c
index 444c99e..2e79eab 100644
--- a/environment.c
+++ b/environment.c
@@ -14,6 +14,7 @@ char git_default_name[MAX_GITNAME];
int trust_executable_bit = 1;
int assume_unchanged = 0;
int prefer_symlink_refs = 0;
+int log_all_ref_updates = 0;
int warn_ambiguous_refs = 1;
int repository_format_version = 0;
char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
diff --git a/refs.c b/refs.c
index 91c8c44..4be75a5 100644
--- a/refs.c
+++ b/refs.c
@@ -302,6 +302,7 @@ static struct ref_lock* lock_ref_sha1_ba
lock->ref_file = strdup(path);
lock->lock_file = strdup(mkpath("%s.lock", lock->ref_file));
+ lock->log_file = strdup(git_path("logs/%s", lock->ref_file + plen));
if (safe_create_leading_directories(lock->lock_file))
die("unable to create directory for %s", lock->lock_file);
@@ -343,9 +344,60 @@ void unlock_ref (struct ref_lock *lock)
free(lock->ref_file);
if (lock->lock_file)
free(lock->lock_file);
+ if (lock->log_file)
+ free(lock->log_file);
free(lock);
}
+static int log_ref_write(struct ref_lock *lock,
+ const unsigned char *sha1, const char *msg)
+{
+ int logfd, written, oflags = O_APPEND | O_WRONLY;
+ unsigned maxlen, len;
+ char *logrec;
+ const char *comitter;
+
+ if (log_all_ref_updates) {
+ if (safe_create_leading_directories(lock->log_file) < 0)
+ return error("unable to create directory for %s",
+ lock->log_file);
+ oflags |= O_CREAT;
+ }
+
+ logfd = open(lock->log_file, oflags, 0666);
+ if (logfd < 0) {
+ if (!log_all_ref_updates && errno == ENOENT)
+ return 0;
+ return error("Unable to append to %s: %s",
+ lock->log_file, strerror(errno));
+ }
+
+ setup_ident();
+ comitter = git_committer_info(1);
+ if (msg) {
+ maxlen = strlen(comitter) + strlen(msg) + 2*40 + 5;
+ logrec = xmalloc(maxlen);
+ len = snprintf(logrec, maxlen, "%s %s %s\t%s\n",
+ sha1_to_hex(lock->old_sha1),
+ sha1_to_hex(sha1),
+ comitter,
+ msg);
+ } else {
+ maxlen = strlen(comitter) + 2*40 + 4;
+ logrec = xmalloc(maxlen);
+ len = snprintf(logrec, maxlen, "%s %s %s\n",
+ sha1_to_hex(lock->old_sha1),
+ sha1_to_hex(sha1),
+ comitter);
+ }
+ written = len <= maxlen ? write(logfd, logrec, len) : -1;
+ free(logrec);
+ close(logfd);
+ if (written != len)
+ return error("Unable to append to %s", lock->log_file);
+ return 0;
+}
+
int write_ref_sha1(struct ref_lock *lock,
const unsigned char *sha1, const char *logmsg)
{
@@ -364,6 +416,10 @@ int write_ref_sha1(struct ref_lock *lock
unlock_ref(lock);
return -1;
}
+ if (log_ref_write(lock, sha1, logmsg) < 0) {
+ unlock_ref(lock);
+ return -1;
+ }
if (rename(lock->lock_file, lock->ref_file) < 0) {
error("Couldn't set %s", lock->ref_file);
unlock_ref(lock);
diff --git a/refs.h b/refs.h
index b7e9df2..43831e9 100644
--- a/refs.h
+++ b/refs.h
@@ -4,6 +4,7 @@ #define REFS_H
struct ref_lock {
char *ref_file;
char *lock_file;
+ char *log_file;
unsigned char old_sha1[20];
int lock_fd;
};
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
new file mode 100644
index 0000000..f338c53
--- /dev/null
+++ b/t/t1400-update-ref.sh
@@ -0,0 +1,112 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Shawn Pearce
+#
+
+test_description='Test git-update-ref and basic ref logging'
+. ./test-lib.sh
+
+Z=0000000000000000000000000000000000000000
+A=1111111111111111111111111111111111111111
+B=2222222222222222222222222222222222222222
+m=refs/heads/master
+
+test_expect_success \
+ "create $m" \
+ 'git-update-ref $m $A &&
+ test $A = $(cat .git/$m)'
+test_expect_success \
+ "create $m" \
+ 'git-update-ref $m $B $A &&
+ test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+test_expect_success \
+ "create $m (by HEAD)" \
+ 'git-update-ref HEAD $A &&
+ test $A = $(cat .git/$m)'
+test_expect_success \
+ "create $m (by HEAD)" \
+ 'git-update-ref HEAD $B $A &&
+ test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+test_expect_failure \
+ '(not) create HEAD with old sha1' \
+ 'git-update-ref HEAD $A $B'
+test_expect_failure \
+ "(not) prior created .git/$m" \
+ 'test -f .git/$m'
+rm -f .git/$m
+
+test_expect_success \
+ "create HEAD" \
+ 'git-update-ref HEAD $A'
+test_expect_failure \
+ '(not) change HEAD with wrong SHA1' \
+ 'git-update-ref HEAD $B $Z'
+test_expect_failure \
+ "(not) changed .git/$m" \
+ 'test $B = $(cat .git/$m)'
+rm -f .git/$m
+
+mkdir -p .git/logs/refs/heads
+touch .git/logs/refs/heads/master
+test_expect_success \
+ "create $m (logged by touch)" \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:30" \
+ git-update-ref HEAD $A -m "Initial Creation" &&
+ test $A = $(cat .git/$m)'
+test_expect_success \
+ "update $m (logged by touch)" \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:31" \
+ git-update-ref HEAD $B $A -m "Switch" &&
+ test $B = $(cat .git/$m)'
+test_expect_success \
+ "set $m (logged by touch)" \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:41" \
+ git-update-ref HEAD $A &&
+ test $A = $(cat .git/$m)'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000 Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000
+EOF
+test_expect_success \
+ "verifying $m's log" \
+ 'diff expect .git/logs/$m'
+rm -rf .git/$m .git/logs expect
+
+test_expect_success \
+ 'enable core.logAllRefUpdates' \
+ 'git-repo-config core.logAllRefUpdates true &&
+ test true = $(git-repo-config --bool --get core.logAllRefUpdates)'
+
+test_expect_success \
+ "create $m (logged by config)" \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:32" \
+ git-update-ref HEAD $A -m "Initial Creation" &&
+ test $A = $(cat .git/$m)'
+test_expect_success \
+ "update $m (logged by config)" \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:33" \
+ git-update-ref HEAD $B $A -m "Switch" &&
+ test $B = $(cat .git/$m)'
+test_expect_success \
+ "set $m (logged by config)" \
+ 'GIT_COMMITTER_DATE="2005-05-26 23:43" \
+ git-update-ref HEAD $A &&
+ test $A = $(cat .git/$m)'
+
+cat >expect <<EOF
+$Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 +0000 Initial Creation
+$A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000 Switch
+$B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000
+EOF
+test_expect_success \
+ "verifying $m's log" \
+ 'diff expect .git/logs/$m'
+rm -f .git/$m .git/logs/$m expect
+
+test_done
--
1.3.2.g7278
^ permalink raw reply related
* [RFC 5/5] Support 'master@2 hours ago' syntax
From: Shawn Pearce @ 2006-05-17 9:56 UTC (permalink / raw)
To: git
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.
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
---
Documentation/git-rev-parse.txt | 6 ++++
refs.c | 53 +++++++++++++++++++++++++++++++++++
refs.h | 3 ++
sha1_name.c | 59 ++++++++++++++++++++++++++++-----------
4 files changed, 104 insertions(+), 17 deletions(-)
efffa32e4896acd7978767b6a856fc7421060040
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index ab896fc..df308c3 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -124,6 +124,12 @@ syntax.
happen to have both heads/master and tags/master, you can
explicitly say 'heads/master' to tell git which one you mean.
+* 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>).
+
* A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e.
'rev{caret}'
diff --git a/refs.c b/refs.c
index 4be75a5..4c99e37 100644
--- a/refs.c
+++ b/refs.c
@@ -429,3 +429,56 @@ int write_ref_sha1(struct ref_lock *lock
unlock_ref(lock);
return 0;
}
+
+int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
+{
+ const char *logfile, *logdata, *logend, *rec, *c;
+ char *tz_c;
+ int logfd, tz;
+ struct stat st;
+ unsigned long date;
+
+ logfile = git_path("logs/%s", ref);
+ logfd = open(logfile, O_RDONLY, 0);
+ if (logfd < 0)
+ die("Unable to read log %s: %s", logfile, strerror(errno));
+ fstat(logfd, &st);
+ if (!st.st_size)
+ die("Log %s is empty.", logfile);
+ logdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+ close(logfd);
+
+ rec = logend = logdata + st.st_size;
+ while (logdata < rec) {
+ if (logdata < rec && *(rec-1) == '\n')
+ rec--;
+ while (logdata < rec && *(rec-1) != '\n')
+ rec--;
+ c = rec;
+ while (c < logend && *c != '>' && *c != '\n')
+ c++;
+ if (c == logend || *c == '\n')
+ die("Log %s is corrupt.", logfile);
+ date = strtoul(c, NULL, 10);
+ if (date <= at_time) {
+ if (get_sha1_hex(rec + 41, sha1))
+ die("Log %s is corrupt.", logfile);
+ munmap((void*)logdata, st.st_size);
+ return 0;
+ }
+ }
+
+ c = logdata;
+ while (c < logend && *c != '>' && *c != '\n')
+ c++;
+ if (c == logend || *c == '\n')
+ die("Log %s is corrupt.", logfile);
+ date = strtoul(c, &tz_c, 10);
+ tz = strtoul(tz_c, NULL, 10);
+ if (get_sha1_hex(logdata, sha1))
+ die("Log %s is corrupt.", logfile);
+ munmap((void*)logdata, st.st_size);
+ fprintf(stderr, "warning: Log %s only goes back to %s.\n",
+ logfile, show_rfc2822_date(date, tz));
+ return 0;
+}
diff --git a/refs.h b/refs.h
index 43831e9..2c854de 100644
--- a/refs.h
+++ b/refs.h
@@ -34,6 +34,9 @@ extern void unlock_ref (struct ref_lock
/** Writes sha1 into the ref specified by the lock. **/
extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
+/** Reads log for the value of ref during at_time. **/
+extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1);
+
/** Returns 0 if target has the right format for a ref. **/
extern int check_ref_format(const char *target);
diff --git a/sha1_name.c b/sha1_name.c
index dc68355..3ac3ab4 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -4,6 +4,7 @@ #include "commit.h"
#include "tree.h"
#include "blob.h"
#include "tree-walk.h"
+#include "refs.h"
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
{
@@ -245,36 +246,60 @@ static int get_sha1_basic(const char *st
"refs/remotes/%.*s/HEAD",
NULL
};
- const char **p;
- const char *warning = "warning: refname '%.*s' is ambiguous.\n";
- char *pathname;
- int already_found = 0;
+ static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
+ const char **p, *pathname;
+ char *real_path = NULL;
+ int refs_found = 0, at_mark;
+ unsigned long at_time = (unsigned long)-1;
unsigned char *this_result;
unsigned char sha1_from_ref[20];
if (len == 40 && !get_sha1_hex(str, sha1))
return 0;
+ /* At a given period of time? "@2 hours ago" */
+ for (at_mark = 1; at_mark < len; at_mark++) {
+ if (str[at_mark] == '@') {
+ int date_len = len - at_mark - 1;
+ char *date_spec = xmalloc(date_len + 1);
+ strncpy(date_spec, str + at_mark + 1, date_len);
+ date_spec[date_len] = 0;
+ at_time = approxidate(date_spec);
+ free(date_spec);
+ len = at_mark;
+ }
+ }
+
/* Accept only unambiguous ref paths. */
if (ambiguous_path(str, len))
return -1;
for (p = fmt; *p; p++) {
- this_result = already_found ? sha1_from_ref : sha1;
- pathname = git_path(*p, len, str);
- if (!read_ref(pathname, this_result)) {
- if (warn_ambiguous_refs) {
- if (already_found)
- fprintf(stderr, warning, len, str);
- already_found++;
- }
- else
- return 0;
+ this_result = refs_found ? sha1_from_ref : sha1;
+ pathname = resolve_ref(git_path(*p, len, str), this_result, 1);
+ if (pathname) {
+ if (!refs_found++)
+ real_path = strdup(pathname);
+ if (!warn_ambiguous_refs)
+ break;
}
}
- if (already_found)
- return 0;
- return -1;
+
+ if (!refs_found)
+ return -1;
+
+ if (warn_ambiguous_refs && refs_found > 1)
+ fprintf(stderr, warning, len, str);
+
+ if (at_time != (unsigned long)-1) {
+ read_ref_at(
+ real_path + strlen(git_path(".")) - 1,
+ at_time,
+ sha1);
+ }
+
+ free(real_path);
+ return 0;
}
static int get_sha1_1(const char *name, int len, unsigned char *sha1);
--
1.3.2.g7278
^ permalink raw reply related
* Re: Git 1.3.2 on Solaris
From: Stefan Pfetzing @ 2006-05-17 10:41 UTC (permalink / raw)
To: Git Mailing List
In-Reply-To: <7v7j4lhup3.fsf@assigned-by-dhcp.cox.net>
Hi Junio,
2006/5/17, Junio C Hamano <junkio@cox.net>:
> If you look at our Makefile, you will see bindir does not have
> to be gitexecdir. The suggestion by Linus is that you set
> bindir to /usr/local/bin or whereever your distribution's
> packaging scheme wants the locally installed software to be that
> is on user's PATH, and gitexecdir to /usr/local/libexec/git
> (again, whereever), _and_ have:
>
> ln -s /usr/bin/gtr /usr/local/libexec/git/tr
> ln -s /usr/bin/gxargs /usr/local/libexec/git/xargs
> ...
Nice, that looks like a solution, but will this also "fix" the tr usage for
the git tests? If so, I'll write a small shellscript to create the links and
so on, and test it on Solaris later today.
bye
dreamind
--
http://www.dreamind.de/
Oroborus and Debian GNU/Linux Developer.
^ permalink raw reply
* Re: [RFC 1/5] Remove unnecessary local in get_ref_sha1.
From: Junio C Hamano @ 2006-05-17 10:43 UTC (permalink / raw)
To: Shawn Pearce; +Cc: git
In-Reply-To: <20060517095446.GB28529@spearce.org>
Shawn Pearce <spearce@spearce.org> writes:
> Remove unnecessary local in get_ref_sha1.
>
> Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
Ack.
^ permalink raw reply
* Re: [RFC 2/5] Improve abstraction of ref lock/write.
From: Junio C Hamano @ 2006-05-17 10:46 UTC (permalink / raw)
To: Shawn Pearce; +Cc: git
In-Reply-To: <20060517095502.GC28529@spearce.org>
Shawn Pearce <spearce@spearce.org> writes:
> Created 'struct ref_lock' to contain the data necessary to perform
> a ref update...
>
> fetch.c | 32 +++++----
> refs.c | 236 ++++++++++++++++++++++++++++++---------------------------------
> refs.h | 24 ++++--
> 3 files changed, 144 insertions(+), 148 deletions(-)
Looks a bit intrusive probably necessary so not necessarily bad
but I'll postpone looking at this for now; maybe tomorrow or
more likely over the weekend.
Eyeballs from people touched the affected files in the past are
greatly appreciated.
^ 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
* 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 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: "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
* [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
* [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
* 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
* 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
* 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: "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: 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: [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
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