diff options
-rw-r--r-- | Documentation/everyday.txt | 42 | ||||
-rw-r--r-- | Documentation/git-p4import.txt | 165 | ||||
-rw-r--r-- | Documentation/git-read-tree.txt | 11 | ||||
-rw-r--r-- | Documentation/git-write-tree.txt | 8 | ||||
-rw-r--r-- | Documentation/tutorial-2.txt | 2 | ||||
-rw-r--r-- | builtin-push.c | 20 | ||||
-rw-r--r-- | builtin-read-tree.c | 71 | ||||
-rw-r--r-- | builtin-rev-list.c | 4 | ||||
-rw-r--r-- | builtin-rev-parse.c | 10 | ||||
-rw-r--r-- | cache-tree.c | 26 | ||||
-rw-r--r-- | cache-tree.h | 2 | ||||
-rwxr-xr-x | contrib/gitview/gitview | 15 | ||||
-rw-r--r-- | contrib/gitview/gitview.txt | 6 | ||||
-rw-r--r-- | fetch-pack.c | 16 | ||||
-rw-r--r-- | git-p4import.py | 357 | ||||
-rw-r--r-- | git.c | 127 | ||||
-rwxr-xr-x | gitk | 1462 | ||||
-rw-r--r-- | pack-objects.c | 69 | ||||
-rwxr-xr-x | t/t0000-basic.sh | 14 | ||||
-rw-r--r-- | write-tree.c | 23 |
20 files changed, 1975 insertions, 475 deletions
diff --git a/Documentation/everyday.txt b/Documentation/everyday.txt index 2ad2d61300..6745ab5fc5 100644 --- a/Documentation/everyday.txt +++ b/Documentation/everyday.txt @@ -336,15 +336,20 @@ master, nor exposed as a part of a stable branch. <11> make sure I did not accidentally rewind master beyond what I already pushed out. "ko" shorthand points at the repository I have at kernel.org, and looks like this: - $ cat .git/remotes/ko - URL: kernel.org:/pub/scm/git/git.git - Pull: master:refs/tags/ko-master - Pull: maint:refs/tags/ko-maint - Push: master - Push: +pu - Push: maint ++ +------------ +$ cat .git/remotes/ko +URL: kernel.org:/pub/scm/git/git.git +Pull: master:refs/tags/ko-master +Pull: maint:refs/tags/ko-maint +Push: master +Push: +pu +Push: maint +------------ ++ In the output from "git show-branch", "master" should have everything "ko-master" has. + <12> push out the bleeding edge. <13> push the tag out, too. @@ -377,6 +382,29 @@ git stream tcp nowait nobody \ + The actual configuration line should be on one line. +Run git-daemon to serve /pub/scm from xinetd.:: ++ +------------ +$ cat /etc/xinetd.d/git-daemon +# default: off +# description: The git server offers access to git repositories +service git +{ + disable = no + type = UNLISTED + port = 9418 + socket_type = stream + wait = no + user = nobody + server = /usr/bin/git-daemon + server_args = --inetd --syslog --export-all --base-path=/pub/scm + log_on_failure += USERID +} +------------ ++ +Check your xinetd(8) documentation and setup, this is from a Fedora system. +Others might be different. + Give push/pull only access to developers.:: + ------------ diff --git a/Documentation/git-p4import.txt b/Documentation/git-p4import.txt new file mode 100644 index 0000000000..b8ff1e9bde --- /dev/null +++ b/Documentation/git-p4import.txt @@ -0,0 +1,165 @@ +git-p4import(1) +=============== + +NAME +---- +git-p4import - Import a Perforce repository into git + + +SYNOPSIS +-------- +`git-p4import` [-q|-v] [--authors <file>] [-t <timezone>] <//p4repo/path> <branch> + +`git-p4import` --stitch <//p4repo/path> + +`git-p4import` + + +DESCRIPTION +----------- +Import a Perforce repository into an existing git repository. When +a <//p4repo/path> and <branch> are specified a new branch with the +given name will be created and the initial import will begin. + +Once the initial import is complete you can do an incremental import +of new commits from the Perforce repository. You do this by checking +out the appropriate git branch and then running `git-p4import` without +any options. + +The standard p4 client is used to communicate with the Perforce +repository; it must be configured correctly in order for `git-p4import` +to operate (see below). + + +OPTIONS +------- +-q:: + Do not display any progress information. + +-v:: + Give extra progress information. + +\--authors:: + Specify an authors file containing a mapping of Perforce user + ids to full names and email addresses (see Notes below). + +\--stitch:: + Import the contents of the given perforce branch into the + currently checked out git branch. + +\--log:: + Store debugging information in the specified file. + +-t:: + Specify that the remote repository is in the specified timezone. + Timezone must be in the format "US/Pacific" or "Europe/London" + etc. You only need to specify this once, it will be saved in + the git config file for the repository. + +<//p4repo/path>:: + The Perforce path that will be imported into the specified branch. + +<branch>:: + The new branch that will be created to hold the Perforce imports. + + +P4 Client +--------- +You must make the `p4` client command available in your $PATH and +configure it to communicate with the target Perforce repository. +Typically this means you must set the "$P4PORT" and "$P4CLIENT" +environment variables. + +You must also configure a `p4` client "view" which maps the Perforce +branch into the top level of your git repository, for example: + +------------ +Client: myhost + +Root: /home/sean/import + +Options: noallwrite clobber nocompress unlocked modtime rmdir + +View: + //public/jam/... //myhost/jam/... +------------ + +With the above `p4` client setup, you could import the "jam" +perforce branch into a branch named "jammy", like so: + +------------ +$ mkdir -p /home/sean/import/jam +$ cd /home/sean/import/jam +$ git init-db +$ git p4import //public/jam jammy +------------ + + +Multiple Branches +----------------- +Note that by creating multiple "views" you can use `git-p4import` +to import additional branches into the same git repository. +However, the `p4` client has a limitation in that it silently +ignores all but the last "view" that maps into the same local +directory. So the following will *not* work: + +------------ +View: + //public/jam/... //myhost/jam/... + //public/other/... //myhost/jam/... + //public/guest/... //myhost/jam/... +------------ + +If you want more than one Perforce branch to be imported into the +same directory you must employ a workaround. A simple option is +to adjust your `p4` client before each import to only include a +single view. + +Another option is to create multiple symlinks locally which all +point to the same directory in your git repository and then use +one per "view" instead of listing the actual directory. + + +Tags +---- +A git tag of the form p4/xx is created for every change imported from +the Perforce repository where xx is the Perforce changeset number. +Therefore after the import you can use git to access any commit by its +Perforce number, eg. git show p4/327. + +The tag associated with the HEAD commit is also how `git-p4import` +determines if their are new changes to incrementally import from the +Perforce repository. + +If you import from a repository with many thousands of changes +you will have an equal number of p4/xxxx git tags. Git tags can +be expensive in terms of disk space and repository operations. +If you don't need to perform further incremental imports, you +may delete the tags. + + +Notes +----- +You can interrupt the import (eg. ctrl-c) at any time and restart it +without worry. + +Author information is automatically determined by querying the +Perforce "users" table using the id associated with each change. +However, if you want to manually supply these mappings you can do +so with the "--authors" option. It accepts a file containing a list +of mappings with each line containing one mapping in the format: + +------------ + perforce_id = Full Name <email@address.com> +------------ + + +Author +------ +Written by Sean Estabrooks <seanlkml@sympatico.ca> + + +GIT +--- +Part of the gitlink:git[7] suite + diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 02c7e99fe6..1c01d00df2 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index SYNOPSIS -------- -'git-read-tree' (<tree-ish> | [[-m [--aggressive]| --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) +'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) DESCRIPTION @@ -63,6 +63,15 @@ OPTIONS * when both sides adds a path identically. The resolution is to add that path. +--prefix=<prefix>/:: + Keep the current index contents, and read the contents + of named tree-ish under directory at `<prefix>`. The + original index file cannot have anything at the path + `<prefix>` itself, and have nothing in `<prefix>/` + directory. Note that the `<prefix>/` value must end + with a slash. + + <tree-ish#>:: The id of the tree object(s) to be read/merged. diff --git a/Documentation/git-write-tree.txt b/Documentation/git-write-tree.txt index 77e12cb949..c85fa89c30 100644 --- a/Documentation/git-write-tree.txt +++ b/Documentation/git-write-tree.txt @@ -8,7 +8,7 @@ git-write-tree - Creates a tree object from the current index SYNOPSIS -------- -'git-write-tree' [--missing-ok] +'git-write-tree' [--missing-ok] [--prefix=<prefix>/] DESCRIPTION ----------- @@ -30,6 +30,12 @@ OPTIONS directory exist in the object database. This option disables this check. +--prefix=<prefix>/:: + Writes a tree object that represents a subdirectory + `<prefix>`. This can be used to write the tree object + for a subproject that is in the named subdirectory. + + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt index 82c692254e..894ca5e06f 100644 --- a/Documentation/tutorial-2.txt +++ b/Documentation/tutorial-2.txt @@ -136,7 +136,7 @@ The "tree" object here refers to the new state of the tree: ------------------------------------------------ $ git ls-tree d0492b36 100644 blob a0423896973644771497bdc03eb99d5281615b51 file.txt -$ git cat-file commit a0423896 +$ git cat-file blob a0423896 hello world! ------------------------------------------------ diff --git a/builtin-push.c b/builtin-push.c index e530022824..66b9407822 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -214,7 +214,7 @@ static int do_push(const char *repo) { const char *uri[MAX_URI]; int i, n; - int remote; + int common_argc; const char **argv; int argc; @@ -231,23 +231,25 @@ static int do_push(const char *repo) argv[argc++] = "--force"; if (execute) argv[argc++] = execute; - if (thin) - argv[argc++] = "--thin"; - remote = argc; - argv[argc++] = "dummy-remote"; - while (refspec_nr--) - argv[argc++] = *refspec++; - argv[argc] = NULL; + common_argc = argc; for (i = 0; i < n; i++) { int error; + int dest_argc = common_argc; + int dest_refspec_nr = refspec_nr; + const char **dest_refspec = refspec; const char *dest = uri[i]; const char *sender = "git-send-pack"; if (!strncmp(dest, "http://", 7) || !strncmp(dest, "https://", 8)) sender = "git-http-push"; + else if (thin) + argv[dest_argc++] = "--thin"; argv[0] = sender; - argv[remote] = dest; + argv[dest_argc++] = dest; + while (dest_refspec_nr--) + argv[dest_argc++] = *dest_refspec++; + argv[dest_argc] = NULL; error = run_command_v(argc, argv); if (!error) continue; diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 085e11e7c4..57bc4516a0 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -24,6 +24,7 @@ static int trivial_merges_only = 0; static int aggressive = 0; static int verbose_update = 0; static volatile int progress_update = 0; +static const char *prefix = NULL; static int head_idx = -1; static int merge_size = 0; @@ -376,6 +377,7 @@ static void check_updates(struct cache_entry **src, int nr) fprintf(stderr, "%4u%% (%u/%u) done\r", percent, cnt, total); last_percent = percent; + progress_update = 0; } } } @@ -411,7 +413,8 @@ static int unpack_trees(merge_fn_t fn) posns[i] = create_tree_entry_list((struct tree *) posn->item); posn = posn->next; } - if (unpack_trees_rec(posns, len, "", fn, &indpos)) + if (unpack_trees_rec(posns, len, prefix ? prefix : "", + fn, &indpos)) return -1; } @@ -761,6 +764,28 @@ static int twoway_merge(struct cache_entry **src) } /* + * Bind merge. + * + * Keep the index entries at stage0, collapse stage1 but make sure + * stage0 does not have anything there. + */ +static int bind_merge(struct cache_entry **src) +{ + struct cache_entry *old = src[0]; + struct cache_entry *a = src[1]; + + if (merge_size != 1) + return error("Cannot do a bind merge of %d trees\n", + merge_size); + if (a && old) + die("Entry '%s' overlaps. Cannot bind.", a->name); + if (!a) + return keep_entry(old); + else + return merged_entry(a, NULL); +} + +/* * One-way merge. * * The rule is: @@ -850,7 +875,7 @@ static void prime_cache_tree(void) } -static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])"; +static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <sha1> [<sha2> [<sha3>]])"; static struct cache_file cache_file; @@ -895,12 +920,27 @@ int cmd_read_tree(int argc, const char **argv, char **envp) continue; } + /* "--prefix=<subdirectory>/" means keep the current index + * entries and put the entries from the tree under the + * given subdirectory. + */ + if (!strncmp(arg, "--prefix=", 9)) { + if (stage || merge || prefix) + usage(read_tree_usage); + prefix = arg + 9; + merge = 1; + stage = 1; + if (read_cache_unmerged()) + die("you need to resolve your current index first"); + continue; + } + /* This differs from "-m" in that we'll silently ignore * unmerged entries and overwrite working tree files that * correspond to them. */ if (!strcmp(arg, "--reset")) { - if (stage || merge) + if (stage || merge || prefix) usage(read_tree_usage); reset = 1; merge = 1; @@ -921,7 +961,7 @@ int cmd_read_tree(int argc, const char **argv, char **envp) /* "-m" stands for "merge", meaning we start in stage 1 */ if (!strcmp(arg, "-m")) { - if (stage || merge) + if (stage || merge || prefix) usage(read_tree_usage); if (read_cache_unmerged()) die("you need to resolve your current index first"); @@ -943,12 +983,31 @@ int cmd_read_tree(int argc, const char **argv, char **envp) if ((update||index_only) && !merge) usage(read_tree_usage); + if (prefix) { + int pfxlen = strlen(prefix); + int pos; + if (prefix[pfxlen-1] != '/') + die("prefix must end with /"); + if (stage != 2) + die("binding merge takes only one tree"); + pos = cache_name_pos(prefix, pfxlen); + if (0 <= pos) + die("corrupt index file"); + pos = -pos-1; + if (pos < active_nr && + !strncmp(active_cache[pos]->name, prefix, pfxlen)) + die("subdirectory '%s' already exists.", prefix); + pos = cache_name_pos(prefix, pfxlen-1); + if (0 <= pos) + die("file '%.*s' already exists.", pfxlen-1, prefix); + } + if (merge) { if (stage < 2) die("just how do you expect me to merge %d trees?", stage-1); switch (stage - 1) { case 1: - fn = oneway_merge; + fn = prefix ? bind_merge : oneway_merge; break; case 2: fn = twoway_merge; @@ -974,7 +1033,7 @@ int cmd_read_tree(int argc, const char **argv, char **envp) * valid cache-tree because the index must match exactly * what came from the tree. */ - if (trees && trees->item && (!merge || (stage == 2))) { + if (trees && trees->item && !prefix && (!merge || (stage == 2))) { cache_tree_free(&active_cache_tree); prime_cache_tree(); } diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 17c04b962d..e885624255 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -135,9 +135,9 @@ static struct object_list **process_tree(struct tree *tree, while (tree_entry(&desc, &entry)) { if (S_ISDIR(entry.mode)) - p = process_tree(lookup_tree(entry.sha1), p, &me, name); + p = process_tree(lookup_tree(entry.sha1), p, &me, entry.path); else - p = process_blob(lookup_blob(entry.sha1), p, &me, name); + p = process_blob(lookup_blob(entry.sha1), p, &me, entry.path); } free(tree->buffer); tree->buffer = NULL; diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index c353a481b0..b27a6d382b 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -15,7 +15,7 @@ #define DO_NONFLAGS 8 static int filter = ~0; -static char *def = NULL; +static const char *def = NULL; #define NORMAL 0 #define REVERSED 1 @@ -111,7 +111,7 @@ static void show_rev(int type, const unsigned char *sha1, const char *name) } /* Output a flag, only if filter allows it. */ -static int show_flag(char *arg) +static int show_flag(const char *arg) { if (!(filter & DO_FLAGS)) return 0; @@ -124,7 +124,7 @@ static int show_flag(char *arg) static void show_default(void) { - char *s = def; + const char *s = def; if (s) { unsigned char sha1[20]; @@ -173,7 +173,7 @@ int cmd_rev_parse(int argc, const char **argv, char **envp) git_config(git_default_config); for (i = 1; i < argc; i++) { - char *arg = argv[i]; + const char *arg = argv[i]; char *dotdot; if (as_is) { @@ -330,7 +330,7 @@ int cmd_rev_parse(int argc, const char **argv, char **envp) if (dotdot) { unsigned char end[20]; char *next = dotdot + 2; - char *this = arg; + const char *this = arg; *dotdot = 0; if (!*next) next = "HEAD"; diff --git a/cache-tree.c b/cache-tree.c index a880c97b38..d9f7e1e3dd 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -529,3 +529,29 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size) return NULL; /* not the whole tree */ return read_one(&buffer, &size); } + +struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path) +{ + while (*path) { + const char *slash; + struct cache_tree_sub *sub; + + slash = strchr(path, '/'); + if (!slash) + slash = path + strlen(path); + /* between path and slash is the name of the + * subtree to look for. + */ + sub = find_subtree(it, path, slash - path, 0); + if (!sub) + return NULL; + it = sub->cache_tree; + if (slash) + while (*slash && *slash == '/') + slash++; + if (!slash || !*slash) + return it; /* prefix ended with slashes */ + path = slash; + } + return it; +} diff --git a/cache-tree.h b/cache-tree.h index 72c64801f5..119407e3a1 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -28,4 +28,6 @@ struct cache_tree *cache_tree_read(const char *buffer, unsigned long size); int cache_tree_fully_valid(struct cache_tree *); int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int); +struct cache_tree *cache_tree_find(struct cache_tree *, const char *); + #endif diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview index b836047cf3..3b6bdceeeb 100755 --- a/contrib/gitview/gitview +++ b/contrib/gitview/gitview @@ -450,6 +450,9 @@ class GitView: self.accel_group = gtk.AccelGroup() self.window.add_accel_group(self.accel_group) self.accel_group.connect_group(0xffc2, 0, gtk.ACCEL_LOCKED, self.refresh); + self.accel_group.connect_group(0xffc1, 0, gtk.ACCEL_LOCKED, self.maximize); + self.accel_group.connect_group(0xffc8, 0, gtk.ACCEL_LOCKED, self.fullscreen); + self.accel_group.connect_group(0xffc9, 0, gtk.ACCEL_LOCKED, self.unfullscreen); self.window.add(self.construct()) @@ -461,6 +464,18 @@ class GitView: self.window.show() return True + def maximize(self, widget, event=None, *arguments, **keywords): + self.window.maximize() + return True + + def fullscreen(self, widget, event=None, *arguments, **keywords): + self.window.fullscreen() + return True + + def unfullscreen(self, widget, event=None, *arguments, **keywords): + self.window.unfullscreen() + return True + def get_bt_sha1(self): """ Update the bt_sha1 dictionary with the respective sha1 details """ diff --git a/contrib/gitview/gitview.txt b/contrib/gitview/gitview.txt index e3bc4f46c2..6924df286e 100644 --- a/contrib/gitview/gitview.txt +++ b/contrib/gitview/gitview.txt @@ -26,8 +26,14 @@ OPTIONS <args> All the valid option for git-rev-list(1) Key Bindings: + F4: + To maximize the window F5: To reread references. + F11: + Full screen + F12: + Leave full screen EXAMPLES ------ diff --git a/fetch-pack.c b/fetch-pack.c index 8daa93d024..8371348556 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -18,6 +18,12 @@ static const char *exec = "git-upload-pack"; #define SEEN (1U << 3) #define POPPED (1U << 4) +/* + * After sending this many "have"s if we do not get any new ACK , we + * give up traversing our history. + */ +#define MAX_IN_VAIN 256 + static struct commit_list *rev_list = NULL; static int non_common_revs = 0, multi_ack = 0, use_thin_pack = 0; @@ -134,6 +140,8 @@ static int find_common(int fd[2], unsigned char *result_sha1, int fetching; int count = 0, flushes = 0, retval; const unsigned char *sha1; + unsigned in_vain = 0; + int got_continue = 0; for_each_ref(rev_list_insert_ref); @@ -172,6 +180,7 @@ static int find_common(int fd[2], unsigned char *result_sha1, packet_write(fd[1], "have %s\n", sha1_to_hex(sha1)); if (verbose) fprintf(stderr, "have %s\n", sha1_to_hex(sha1)); + in_vain++; if (!(31 & ++count)) { int ack; @@ -200,9 +209,16 @@ static int find_common(int fd[2], unsigned char *result_sha1, lookup_commit(result_sha1); mark_common(commit, 0, 1); retval = 0; + in_vain = 0; + got_continue = 1; } } while (ack); flushes--; + if (got_continue && MAX_IN_VAIN < in_vain) { + if (verbose) + fprintf(stderr, "giving up\n"); + break; /* give up */ + } } } done: diff --git a/git-p4import.py b/git-p4import.py new file mode 100644 index 0000000000..74172ab450 --- /dev/null +++ b/git-p4import.py @@ -0,0 +1,357 @@ +#!/usr/bin/python +# +# This tool is copyright (c) 2006, Sean Estabrooks. +# It is released under the Gnu Public License, version 2. +# +# Import Perforce branches into Git repositories. +# Checking out the files is done by calling the standard p4 +# client which you must have properly configured yourself +# + +import marshal +import os +import sys +import time +import getopt + +from signal import signal, \ + SIGPIPE, SIGINT, SIG_DFL, \ + default_int_handler + +signal(SIGPIPE, SIG_DFL) +s = signal(SIGINT, SIG_DFL) +if s != default_int_handler: + signal(SIGINT, s) + + +def die(msg, *args): + for a in args: + msg = "%s %s" % (msg, a) + print "git-p4import fatal error:", msg + sys.exit(1) + +def usage(): + print "USAGE: git-p4import [-q|-v] [--authors=<file>] [-t <timezone>] [//p4repo/path <branch>]" + sys.exit(1) + +verbosity = 1 +logfile = "/dev/null" +ignore_warnings = False +stitch = 0 + +def report(level, msg, *args): + global verbosity + global logfile + for a in args: + msg = "%s %s" % (msg, a) + fd = open(logfile, "a") + fd.writelines(msg) + fd.close() + if level <= verbosity: + print msg + +class p4_command: + def __init__(self, _repopath): + try: + global logfile + self.userlist = {} + if _repopath[-1] == '/': + self.repopath = _repopath[:-1] + else: + self.repopath = _repopath + if self.repopath[-4:] != "/...": + self.repopath= "%s/..." % self.repopath + f=os.popen('p4 -V 2>>%s'%logfile, 'rb') + a = f.readlines() + if f.close(): + raise + except: + die("Could not find the \"p4\" command") + + def p4(self, cmd, *args): + global logfile + cmd = "%s %s" % (cmd, ' '.join(args)) + report(2, "P4:", cmd) + f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb') + list = [] + while 1: + try: + list.append(marshal.load(f)) + except EOFError: + break + self.ret = f.close() + return list + + def sync(self, id, force=False, trick=False, test=False): + if force: + ret = self.p4("sync -f %s@%s"%(self.repopath, id))[0] + elif trick: + ret = self.p4("sync -k %s@%s"%(self.repopath, id))[0] + elif test: + ret = self.p4("sync -n %s@%s"%(self.repopath, id))[0] + else: + ret = self.p4("sync %s@%s"%(self.repopath, id))[0] + if ret['code'] == "error": + data = ret['data'].upper() + if data.find('VIEW') > 0: + die("Perforce reports %s is not in client view"% self.repopath) + elif data.find('UP-TO-DATE') < 0: + die("Could not sync files from perforce", self.repopath) + + def changes(self, since=0): + try: + list = [] + for rec in self.p4("changes %s@%s,#head" % (self.repopath, since+1)): + list.append(rec['change']) + list.reverse() + return list + except: + return [] + + def authors(self, filename): + f=open(filename) + for l in f.readlines(): + self.userlist[l[:l.find('=')].rstrip()] = \ + (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')]) + f.close() + for f,e in self.userlist.items(): + report(2, f, ":", e[0], " <", e[1], ">") + + def _get_user(self, id): + if not self.userlist.has_key(id): + try: + user = self.p4("users", id)[0] + self.userlist[id] = (user['FullName'], user['Email']) + except: + self.userlist[id] = (id, "") + return self.userlist[id] + + def _format_date(self, ticks): + symbol='+' + name = time.tzname[0] + offset = time.timezone + if ticks[8]: + name = time.tzname[1] + offset = time.altzone + if offset < 0: + offset *= -1 + symbol = '-' + localo = "%s%02d%02d %s" % (symbol, offset / 3600, offset % 3600, name) + tickso = time.strftime("%a %b %d %H:%M:%S %Y", ticks) + return "%s %s" % (tickso, localo) + + def where(self): + try: + return self.p4("where %s" % self.repopath)[-1]['path'] + except: + return "" + + def describe(self, num): + desc = self.p4("describe -s", num)[0] + self.msg = desc['desc'] + self.author, self.email = self._get_user(desc['user']) + self.date = self._format_date(time.localtime(long(desc['time']))) + return self + +class git_command: + def __init__(self): + try: + self.version = self.git("--version")[0][12:].rstrip() + except: + die("Could not find the \"git\" command") + try: + self.gitdir = self.get_single("rev-parse --git-dir") + report(2, "gdir:", self.gitdir) + except: + die("Not a git repository... did you forget to \"git init-db\" ?") + try: + self.cdup = self.get_single("rev-parse --show-cdup") + if self.cdup != "": + os.chdir(self.cdup) + self.topdir = os.getcwd() + report(2, "topdir:", self.topdir) + except: + die("Could not find top git directory") + + def git(self, cmd): + global logfile + report(2, "GIT:", cmd) + f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb') + r=f.readlines() + self.ret = f.close() + return r + + def get_single(self, cmd): + return self.git(cmd)[0].rstrip() + + def current_branch(self): + try: + testit = self.git("rev-parse --verify HEAD")[0] + return self.git("symbolic-ref HEAD")[0][11:].rstrip() + except: + return None + + def get_config(self, variable): + try: + return self.git("repo-config --get %s" % variable)[0].rstrip() + except: + return None + + def set_config(self, variable, value): + try: + self.git("repo-config %s %s"%(variable, value) ) + except: + die("Could not set %s to " % variable, value) + + def make_tag(self, name, head): + self.git("tag -f %s %s"%(name,head)) + + def top_change(self, branch): + try: + a=self.get_single("name-rev --tags refs/heads/%s" % branch) + loc = a.find(' tags/') + 6 + if a[loc:loc+3] != "p4/": + raise + return int(a[loc+3:][:-2]) + except: + return 0 + + def update_index(self): + self.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin") + + def checkout(self, branch): + self.git("checkout %s" % branch) + + def repoint_head(self, branch): + self.git("symbolic-ref HEAD refs/heads/%s" % branch) + + def remove_files(self): + self.git("ls-files | xargs rm") + + def clean_directories(self): + self.git("clean -d") + + def fresh_branch(self, branch): + report(1, "Creating new branch", branch) + self.git("ls-files | xargs rm") + os.remove(".git/index") + self.repoint_head(branch) + self.git("clean -d") + + def basedir(self): + return self.topdir + + def commit(self, author, email, date, msg, id): + self.update_index() + fd=open(".msg", "w") + fd.writelines(msg) + fd.close() + try: + current = self.get_single("rev-parse --verify HEAD") + head = "-p HEAD" + except: + current = "" + head = "" + tree = self.get_single("write-tree") + for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]: + os.environ['GIT_AUTHOR_%s'%r] = l + os.environ['GIT_COMMITTER_%s'%r] = l + commit = self.get_single("commit-tree %s %s < .msg" % (tree,head)) + os.remove(".msg") + self.make_tag("p4/%s"%id, commit) + self.git("update-ref HEAD %s %s" % (commit, current) ) + + +try: + opts, args = getopt.getopt(sys.argv[1:], "qhvt:", + ["authors=","help","stitch=","timezone=","log=","ignore"]) +except getopt.GetoptError: + usage() + +for o, a in opts: + if o == "-q": + verbosity = 0 + if o == "-v": + verbosity += 1 + if o in ("--log"): + logfile = a + if o in ("-h", "--help"): + usage() + if o in ("--ignore"): + ignore_warnings = True + +git = git_command() +branch=git.current_branch() + +for o, a in opts: + if o in ("-t", "--timezone"): + git.set_config("perforce.timezone", a) + if o in ("--stitch"): + git.set_config("perforce.%s.path" % branch, a) + stitch = 1 + +if len(args) == 2: + branch = args[1] + git.checkout(branch) + if branch == git.current_branch(): + die("Branch %s already exists!" % branch) + report(1, "Setting perforce to ", args[0]) + git.set_config("perforce.%s.path" % branch, args[0]) +elif len(args) != 0: + die("You must specify the perforce //depot/path and git branch") + +p4path = git.get_config("perforce.%s.path" % branch) +if p4path == None: + die("Do not know Perforce //depot/path for git branch", branch) + +p4 = p4_command(p4path) + +for o, a in opts: + if o in ("-a", "--authors"): + p4.authors(a) + +localdir = git.basedir() +if p4.where()[:len(localdir)] != localdir: + report(1, "**WARNING** Appears p4 client is misconfigured") + report(1, " for sync from %s to %s" % (p4.repopath, localdir)) + if ignore_warnings != True: + die("Reconfigure or use \"--ignore\" on command line") + +if stitch == 0: + top = git.top_change(branch) +else: + top = 0 +changes = p4.changes(top) +count = len(changes) +if count == 0: + report(1, "Already up to date...") + sys.exit(0) + +ptz = git.get_config("perforce.timezone") +if ptz: + report(1, "Setting timezone to", ptz) + os.environ['TZ'] = ptz + time.tzset() + +if stitch == 1: + git.remove_files() + git.clean_directories() + p4.sync(changes[0], force=True) +elif top == 0 and branch != git.current_branch(): + p4.sync(changes[0], test=True) + report(1, "Creating new initial commit"); + git.fresh_branch(branch) + p4.sync(changes[0], force=True) +else: + p4.sync(changes[0], trick=True) + +report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch)) +for id in changes: + report(1, "Importing changeset", id) + change = p4.describe(id) + p4.sync(id) + git.commit(change.author, change.email, change.date, change.msg, id) + if stitch == 1: + git.clean_directories() + stitch = 0 + @@ -10,6 +10,7 @@ #include <stdarg.h> #include "git-compat-util.h" #include "exec_cmd.h" +#include "cache.h" #include "builtin.h" @@ -32,6 +33,113 @@ static void prepend_to_path(const char *dir, int len) setenv("PATH", path, 1); } +static const char *alias_command; +static char *alias_string = NULL; + +static int git_alias_config(const char *var, const char *value) +{ + if (!strncmp(var, "alias.", 6) && !strcmp(var + 6, alias_command)) { + alias_string = strdup(value); + } + return 0; +} + +static int split_cmdline(char *cmdline, const char ***argv) +{ + int src, dst, count = 0, size = 16; + char quoted = 0; + + *argv = malloc(sizeof(char*) * size); + + /* split alias_string */ + (*argv)[count++] = cmdline; + for (src = dst = 0; cmdline[src];) { + char c = cmdline[src]; + if (!quoted && isspace(c)) { + cmdline[dst++] = 0; + while (cmdline[++src] + && isspace(cmdline[src])) + ; /* skip */ + if (count >= size) { + size += 16; + *argv = realloc(*argv, sizeof(char*) * size); + } + (*argv)[count++] = cmdline + dst; + } else if(!quoted && (c == '\'' || c == '"')) { + quoted = c; + src++; + } else if (c == quoted) { + quoted = 0; + src++; + } else { + if (c == '\\' && quoted != '\'') { + src++; + c = cmdline[src]; + if (!c) { + free(*argv); + *argv = NULL; + return error("cmdline ends with \\"); + } + } + cmdline[dst++] = c; + src++; + } + } + + cmdline[dst] = 0; + + if (quoted) { + free(*argv); + *argv = NULL; + return error("unclosed quote"); + } + + return count; +} + +static int handle_alias(int *argcp, const char ***argv) +{ + int nongit = 0, ret = 0; + const char *subdir; + + subdir = setup_git_directory_gently(&nongit); + if (!nongit) { + int count; + const char** new_argv; + + alias_command = (*argv)[0]; + git_config(git_alias_config); + if (alias_string) { + + count = split_cmdline(alias_string, &new_argv); + + if (count < 1) + die("empty alias for %s", alias_command); + + if (!strcmp(alias_command, new_argv[0])) + die("recursive alias: %s", alias_command); + + /* insert after command name */ + if (*argcp > 1) { + new_argv = realloc(new_argv, sizeof(char*) * + (count + *argcp - 1)); + memcpy(new_argv + count, *argv, sizeof(char*) * + (*argcp - 1)); + } + + *argv = new_argv; + *argcp += count - 1; + + ret = 1; + } + } + + if (subdir) + chdir(subdir); + + return ret; +} + const char git_version_string[] = GIT_VERSION; static void handle_internal_command(int argc, const char **argv, char **envp) @@ -94,6 +202,7 @@ int main(int argc, const char **argv, char **envp) char *slash = strrchr(cmd, '/'); char git_command[PATH_MAX + 1]; const char *exec_path = NULL; + int done_alias = 0; /* * Take the basename of argv[0] as the command @@ -178,11 +287,21 @@ int main(int argc, const char **argv, char **envp) exec_path = git_exec_path(); prepend_to_path(exec_path, strlen(exec_path)); - /* See if it's an internal command */ - handle_internal_command(argc, argv, envp); + while (1) { + /* See if it's an internal command */ + handle_internal_command(argc, argv, envp); - /* .. then try the external ones */ - execv_git_cmd(argv); + /* .. then try the external ones */ + execv_git_cmd(argv); + + /* It could be an alias -- this works around the insanity + * of overriding "git log" with "git show" by having + * alias.log = show + */ + if (done_alias || !handle_alias(&argc, &argv)) + break; + done_alias = 1; + } if (errno == ENOENT) cmd_usage(0, exec_path, "'%s' is not a git-command", cmd); @@ -187,7 +187,7 @@ proc getcommitlines {fd view} { if {$view == $curview} { layoutmore } elseif {[info exists hlview] && $view == $hlview} { - highlightmore + vhighlightmore } } if {[clock clicks -milliseconds] >= $nextupdate} { @@ -223,7 +223,7 @@ proc readcommit {id} { proc updatecommits {} { global viewdata curview phase displayorder - global children commitrow + global children commitrow selectedline thickerline if {$phase ne {}} { stop_rev_list @@ -235,6 +235,8 @@ proc updatecommits {} { catch {unset commitrow($n,$id)} } set curview -1 + catch {unset selectedline} + catch {unset thickerline} catch {unset viewdata($n)} readrefs showview $n @@ -381,6 +383,8 @@ proc makewindow {} { global entries sha1entry sha1string sha1but global maincursor textcursor curtextcursor global rowctxmenu mergemax wrapcomment + global highlight_files gdttype + global searchstring sstring menu .bar .bar add cascade -label "File" -menu .bar.file @@ -396,20 +400,14 @@ proc makewindow {} { .bar.edit configure -font $uifont menu .bar.view -font $uifont - menu .bar.view.hl -font $uifont -tearoff 0 .bar add cascade -label "View" -menu .bar.view .bar.view add command -label "New view..." -command {newview 0} .bar.view add command -label "Edit view..." -command editview \ -state disabled .bar.view add command -label "Delete view" -command delview -state disabled - .bar.view add cascade -label "Highlight" -menu .bar.view.hl .bar.view add separator .bar.view add radiobutton -label "All files" -command {showview 0} \ -variable selectedview -value 0 - .bar.view.hl add command -label "New view..." -command {newview 1} - .bar.view.hl add command -label "Remove" -command delhighlight \ - -state disabled - .bar.view.hl add separator menu .bar.help .bar add cascade -label "Help" -menu .bar.help @@ -436,6 +434,8 @@ proc makewindow {} { } frame .ctop.top frame .ctop.top.bar + frame .ctop.top.lbar + pack .ctop.top.lbar -side bottom -fill x pack .ctop.top.bar -side bottom -fill x set cscroll .ctop.top.csb scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0 @@ -497,31 +497,74 @@ proc makewindow {} { set findstring {} set fstring .ctop.top.bar.findstring lappend entries $fstring - entry $fstring -width 30 -font $textfont -textvariable findstring -font $textfont + entry $fstring -width 30 -font $textfont -textvariable findstring + trace add variable findstring write find_change pack $fstring -side left -expand 1 -fill x set findtype Exact set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \ findtype Exact IgnCase Regexp] + trace add variable findtype write find_change .ctop.top.bar.findtype configure -font $uifont .ctop.top.bar.findtype.menu configure -font $uifont set findloc "All fields" tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \ - Comments Author Committer Files Pickaxe + Comments Author Committer + trace add variable findloc write find_change .ctop.top.bar.findloc configure -font $uifont .ctop.top.bar.findloc.menu configure -font $uifont - pack .ctop.top.bar.findloc -side right pack .ctop.top.bar.findtype -side right - # for making sure type==Exact whenever loc==Pickaxe - trace add variable findloc write findlocchange + + label .ctop.top.lbar.flabel -text "Highlight: Commits " \ + -font $uifont + pack .ctop.top.lbar.flabel -side left -fill y + set gdttype "touching paths:" + set gm [tk_optionMenu .ctop.top.lbar.gdttype gdttype "touching paths:" \ + "adding/removing string:"] + trace add variable gdttype write hfiles_change + $gm conf -font $uifont + .ctop.top.lbar.gdttype conf -font $uifont + pack .ctop.top.lbar.gdttype -side left -fill y + entry .ctop.top.lbar.fent -width 25 -font $textfont \ + -textvariable highlight_files + trace add variable highlight_files write hfiles_change + lappend entries .ctop.top.lbar.fent + pack .ctop.top.lbar.fent -side left -fill x -expand 1 + label .ctop.top.lbar.vlabel -text " OR in view" -font $uifont + pack .ctop.top.lbar.vlabel -side left -fill y + global viewhlmenu selectedhlview + set viewhlmenu [tk_optionMenu .ctop.top.lbar.vhl selectedhlview None] + $viewhlmenu entryconf 0 -command delvhighlight + $viewhlmenu conf -font $uifont + .ctop.top.lbar.vhl conf -font $uifont + pack .ctop.top.lbar.vhl -side left -fill y + label .ctop.top.lbar.rlabel -text " OR " -font $uifont + pack .ctop.top.lbar.rlabel -side left -fill y + global highlight_related + set m [tk_optionMenu .ctop.top.lbar.relm highlight_related None \ + "Descendent" "Not descendent" "Ancestor" "Not ancestor"] + $m conf -font $uifont + .ctop.top.lbar.relm conf -font $uifont + trace add variable highlight_related write vrel_change + pack .ctop.top.lbar.relm -side left -fill y panedwindow .ctop.cdet -orient horizontal .ctop add .ctop.cdet frame .ctop.cdet.left + frame .ctop.cdet.left.bot + pack .ctop.cdet.left.bot -side bottom -fill x + button .ctop.cdet.left.bot.search -text "Search" -command dosearch \ + -font $uifont + pack .ctop.cdet.left.bot.search -side left -padx 5 + set sstring .ctop.cdet.left.bot.sstring + entry $sstring -width 20 -font $textfont -textvariable searchstring + lappend entries $sstring + trace add variable searchstring write incrsearch + pack $sstring -side left -expand 1 -fill x set ctext .ctop.cdet.left.ctext text $ctext -bg white -state disabled -font $textfont \ -width $geometry(ctextw) -height $geometry(ctexth) \ - -yscrollcommand {.ctop.cdet.left.sb set} -wrap none + -yscrollcommand scrolltext -wrap none scrollbar .ctop.cdet.left.sb -command "$ctext yview" pack .ctop.cdet.left.sb -side right -fill y pack $ctext -side left -fill both -expand 1 @@ -574,6 +617,7 @@ proc makewindow {} { pack $cflist -side left -fill both -expand 1 $cflist tag configure highlight \ -background [$cflist cget -selectbackground] + $cflist tag configure bold -font [concat $mainfont bold] .ctop.cdet add .ctop.cdet.right bind .ctop.cdet <Configure> {resizecdetpanes %W %w} @@ -589,6 +633,8 @@ proc makewindow {} { bindkey <End> sellastline bind . <Key-Up> "selnextline -1" bind . <Key-Down> "selnextline 1" + bind . <Shift-Key-Up> "next_highlight -1" + bind . <Shift-Key-Down> "next_highlight 1" bindkey <Key-Right> "goforw" bindkey <Key-Left> "goback" bind . <Key-Prior> "selnextpage -1" @@ -620,7 +666,8 @@ proc makewindow {} { bind . <Control-q> doquit bind . <Control-f> dofind bind . <Control-g> {findnext 0} - bind . <Control-r> findprev + bind . <Control-r> dosearchback + bind . <Control-s> dosearch bind . <Control-equal> {incrfont 1} bind . <Control-KP_Add> {incrfont 1} bind . <Control-minus> {incrfont -1} @@ -665,6 +712,7 @@ proc canvscan {op w x y} { proc scrollcanv {cscroll f0 f1} { $cscroll set $f0 $f1 drawfrac $f0 $f1 + flushhighlights } # when we make a key binding for the toplevel, make sure @@ -695,7 +743,7 @@ proc click {w} { proc savestuff {w} { global canv canv2 canv3 ctext cflist mainfont textfont uifont global stuffsaved findmergefiles maxgraphpct - global maxwidth + global maxwidth showneartags global viewname viewfiles viewargs viewperm nextviewnum global cmitmode wrapcomment @@ -711,6 +759,7 @@ proc savestuff {w} { puts $f [list set maxwidth $maxwidth] puts $f [list set cmitmode $cmitmode] puts $f [list set wrapcomment $wrapcomment] + puts $f [list set showneartags $showneartags] puts $f "set geometry(width) [winfo width .ctop]" puts $f "set geometry(height) [winfo height .ctop]" puts $f "set geometry(canv1) [expr {[winfo width $canv]-2}]" @@ -848,6 +897,8 @@ Gitk key bindings: <Ctrl-Down> Scroll commit list down one line <Ctrl-PageUp> Scroll commit list up one page <Ctrl-PageDown> Scroll commit list down one page +<Shift-Up> Move to previous highlighted line +<Shift-Down> Move to next highlighted line <Delete>, b Scroll diff view up one page <Backspace> Scroll diff view up one page <Space> Scroll diff view down one page @@ -855,11 +906,12 @@ u Scroll diff view up 18 lines d Scroll diff view down 18 lines <Ctrl-F> Find <Ctrl-G> Move to next find hit -<Ctrl-R> Move to previous find hit <Return> Move to next find hit / Move to next find hit, or redo find ? Move to previous find hit f Scroll diff view to next file +<Ctrl-S> Search for next hit in diff view +<Ctrl-R> Search for previous hit in diff view <Ctrl-KP+> Increase font size <Ctrl-plus> Increase font size <Ctrl-KP-> Decrease font size @@ -926,7 +978,7 @@ proc treeview {w l openlevs} { $w insert end $str $w image create end -align center -image $bm -padx 1 \ -name a:$ix - $w insert end $d + $w insert end $d [highlight_tag $prefix] $w mark set s:$ix "end -1c" $w mark gravity s:$ix left } @@ -938,7 +990,7 @@ proc treeview {w l openlevs} { set str "\n" for {set i 0} {$i < $lev} {incr i} {append str "\t"} $w insert end $str - $w insert end $tail + $w insert end $tail [highlight_tag $f] } lappend treecontents($prefix) $tail } @@ -975,6 +1027,22 @@ proc linetoelt {l} { } } +proc highlight_tree {y prefix} { + global treeheight treecontents cflist + + foreach e $treecontents($prefix) { + set path $prefix$e + if {[highlight_tag $path] ne {}} { + $cflist tag add bold $y.0 "$y.0 lineend" + } + incr y + if {[string index $e end] eq "/" && $treeheight($path) > 1} { + set y [highlight_tree $y $path] + } + } + return $y +} + proc treeclosedir {w dir} { global treediropen treeheight treeparent treeindex @@ -1008,8 +1076,8 @@ proc treeopendir {w dir} { incr treeheight($x) $n } foreach e $treecontents($dir) { + set de $dir$e if {[string index $e end] eq "/"} { - set de $dir$e set iy $treeindex($de) $w mark set d:$iy e:$ix $w mark gravity d:$iy left @@ -1017,13 +1085,13 @@ proc treeopendir {w dir} { set treediropen($de) 0 $w image create e:$ix -align center -image tri-rt -padx 1 \ -name a:$iy - $w insert e:$ix $e + $w insert e:$ix $e [highlight_tag $de] $w mark set s:$iy e:$ix $w mark gravity s:$iy left set treeheight($de) 1 } else { $w insert e:$ix $str - $w insert e:$ix $e + $w insert e:$ix $e [highlight_tag $de] } } $w mark gravity e:$ix left @@ -1119,20 +1187,56 @@ proc init_flist {first} { set difffilestart {} } -proc add_flist {fl} { - global flistmode cflist +proc highlight_tag {f} { + global highlight_paths + + foreach p $highlight_paths { + if {[string match $p $f]} { + return "bold" + } + } + return {} +} + +proc highlight_filelist {} { + global cmitmode cflist $cflist conf -state normal - if {$flistmode eq "flat"} { - foreach f $fl { - $cflist insert end "\n$f" + if {$cmitmode ne "tree"} { + set end [lindex [split [$cflist index end] .] 0] + for {set l 2} {$l < $end} {incr l} { + set line [$cflist get $l.0 "$l.0 lineend"] + if {[highlight_tag $line] ne {}} { + $cflist tag add bold $l.0 "$l.0 lineend" + } } + } else { + highlight_tree 2 {} + } + $cflist conf -state disabled +} + +proc unhighlight_filelist {} { + global cflist + + $cflist conf -state normal + $cflist tag remove bold 1.0 end + $cflist conf -state disabled +} + +proc add_flist {fl} { + global cflist + + $cflist conf -state normal + foreach f $fl { + $cflist insert end "\n" + $cflist insert end $f [highlight_tag $f] } $cflist conf -state disabled } proc sel_flist {w x y} { - global flistmode ctext difffilestart cflist cflist_top cmitmode + global ctext difffilestart cflist cflist_top cmitmode if {$cmitmode eq "tree"} return if {![info exists cflist_top]} return @@ -1315,25 +1419,27 @@ proc vieweditor {top n title} { focus $top.t } -proc doviewmenu {m first cmd op args} { +proc doviewmenu {m first cmd op argv} { set nmenu [$m index end] for {set i $first} {$i <= $nmenu} {incr i} { if {[$m entrycget $i -command] eq $cmd} { - eval $m $op $i $args + eval $m $op $i $argv break } } } proc allviewmenus {n op args} { + global viewhlmenu + doviewmenu .bar.view 7 [list showview $n] $op $args - doviewmenu .bar.view.hl 3 [list addhighlight $n] $op $args + doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args } proc newviewok {top n} { global nextviewnum newviewperm newviewname newishighlight global viewname viewfiles viewperm selectedview curview - global viewargs newviewargs + global viewargs newviewargs viewhlmenu if {[catch { set newargs [shellsplit $newviewargs($n)] @@ -1361,14 +1467,17 @@ proc newviewok {top n} { if {!$newishighlight} { after idle showview $n } else { - after idle addhighlight $n + after idle addvhighlight $n } } else { # editing an existing view set viewperm($n) $newviewperm($n) if {$newviewname($n) ne $viewname($n)} { set viewname($n) $newviewname($n) - allviewmenus $n entryconf -label $viewname($n) + doviewmenu .bar.view 7 [list showview $n] \ + entryconf [list -label $viewname($n)] + doviewmenu $viewhlmenu 1 [list addvhighlight $n] \ + entryconf [list -label $viewname($n) -value $viewname($n)] } if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} { set viewfiles($n) $files @@ -1382,9 +1491,13 @@ proc newviewok {top n} { } proc delview {} { - global curview viewdata viewperm + global curview viewdata viewperm hlview selectedhlview if {$curview == 0} return + if {[info exists hlview] && $hlview == $curview} { + set selectedhlview None + unset hlview + } allviewmenus $curview delete set viewdata($curview) {} set viewperm($curview) 0 @@ -1392,12 +1505,12 @@ proc delview {} { } proc addviewmenu {n} { - global viewname + global viewname viewhlmenu .bar.view add radiobutton -label $viewname($n) \ -command [list showview $n] -variable selectedview -value $n - .bar.view.hl add radiobutton -label $viewname($n) \ - -command [list addhighlight $n] -variable selectedhlview -value $n + $viewhlmenu add radiobutton -label $viewname($n) \ + -command [list addvhighlight $n] -variable selectedhlview } proc flatten {var} { @@ -1429,8 +1542,9 @@ proc showview {n} { global pending_select phase global commitidx rowlaidout rowoptim linesegends global commfd nextupdate - global selectedview hlview selectedhlview + global selectedview global vparentlist vchildlist vdisporder vcmitlisted + global hlview selectedhlview if {$n == $curview} return set selid {} @@ -1469,14 +1583,15 @@ proc showview {n} { catch {unset matchinglines} catch {unset treediffs} clear_display + if {[info exists hlview] && $hlview == $n} { + unset hlview + set selectedhlview None + } set curview $n set selectedview $n - set selectedhlview -1 .bar.view entryconf 2 -state [expr {$n == 0? "disabled": "normal"}] .bar.view entryconf 3 -state [expr {$n == 0? "disabled": "normal"}] - catch {unset hlview} - .bar.view.hl entryconf 1 -state disabled if {![info exists viewdata($n)]} { set pending_select $selid @@ -1541,18 +1656,75 @@ proc showview {n} { } } -proc addhighlight {n} { - global hlview curview viewdata highlighted highlightedrows - global selectedhlview +# Stuff relating to the highlighting facility + +proc ishighlighted {row} { + global vhighlights fhighlights nhighlights rhighlights + + if {[info exists nhighlights($row)] && $nhighlights($row) > 0} { + return $nhighlights($row) + } + if {[info exists vhighlights($row)] && $vhighlights($row) > 0} { + return $vhighlights($row) + } + if {[info exists fhighlights($row)] && $fhighlights($row) > 0} { + return $fhighlights($row) + } + if {[info exists rhighlights($row)] && $rhighlights($row) > 0} { + return $rhighlights($row) + } + return 0 +} + +proc bolden {row font} { + global canv linehtag selectedline boldrows + + lappend boldrows $row + $canv itemconf $linehtag($row) -font $font + if {[info exists selectedline] && $row == $selectedline} { + $canv delete secsel + set t [eval $canv create rect [$canv bbox $linehtag($row)] \ + -outline {{}} -tags secsel \ + -fill [$canv cget -selectbackground]] + $canv lower $t + } +} + +proc bolden_name {row font} { + global canv2 linentag selectedline boldnamerows + + lappend boldnamerows $row + $canv2 itemconf $linentag($row) -font $font + if {[info exists selectedline] && $row == $selectedline} { + $canv2 delete secsel + set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \ + -outline {{}} -tags secsel \ + -fill [$canv2 cget -selectbackground]] + $canv2 lower $t + } +} + +proc unbolden {} { + global mainfont boldrows + + set stillbold {} + foreach row $boldrows { + if {![ishighlighted $row]} { + bolden $row $mainfont + } else { + lappend stillbold $row + } + } + set boldrows $stillbold +} + +proc addvhighlight {n} { + global hlview curview viewdata vhl_done vhighlights commitidx if {[info exists hlview]} { - delhighlight + delvhighlight } set hlview $n - set selectedhlview $n - .bar.view.hl entryconf 1 -state normal - set highlighted($n) 0 - set highlightedrows {} if {$n != $curview && ![info exists viewdata($n)]} { set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}] set vparentlist($n) {} @@ -1560,34 +1732,25 @@ proc addhighlight {n} { set vdisporder($n) {} set vcmitlisted($n) {} start_rev_list $n - } else { - highlightmore + } + set vhl_done $commitidx($hlview) + if {$vhl_done > 0} { + drawvisible } } -proc delhighlight {} { - global hlview highlightedrows canv linehtag mainfont - global selectedhlview selectedline +proc delvhighlight {} { + global hlview vhighlights if {![info exists hlview]} return unset hlview - set selectedhlview {} - .bar.view.hl entryconf 1 -state disabled - foreach l $highlightedrows { - $canv itemconf $linehtag($l) -font $mainfont - if {$l == $selectedline} { - $canv delete secsel - set t [eval $canv create rect [$canv bbox $linehtag($l)] \ - -outline {{}} -tags secsel \ - -fill [$canv cget -selectbackground]] - $canv lower $t - } - } + catch {unset vhighlights} + unbolden } -proc highlightmore {} { - global hlview highlighted commitidx highlightedrows linehtag mainfont - global displayorder vdisporder curview canv commitrow selectedline +proc vhighlightmore {} { + global hlview vhl_done commitidx vhighlights + global displayorder vdisporder curview mainfont set font [concat $mainfont bold] set max $commitidx($hlview) @@ -1596,25 +1759,399 @@ proc highlightmore {} { } else { set disp $vdisporder($hlview) } - for {set i $highlighted($hlview)} {$i < $max} {incr i} { + set vr [visiblerows] + set r0 [lindex $vr 0] + set r1 [lindex $vr 1] + for {set i $vhl_done} {$i < $max} {incr i} { set id [lindex $disp $i] if {[info exists commitrow($curview,$id)]} { set row $commitrow($curview,$id) - if {[info exists linehtag($row)]} { - $canv itemconf $linehtag($row) -font $font - lappend highlightedrows $row - if {$row == $selectedline} { - $canv delete secsel - set t [eval $canv create rect \ - [$canv bbox $linehtag($row)] \ - -outline {{}} -tags secsel \ - -fill [$canv cget -selectbackground]] - $canv lower $t + if {$r0 <= $row && $row <= $r1} { + if {![highlighted $row]} { + bolden $row $font + } + set vhighlights($row) 1 + } + } + } + set vhl_done $max +} + +proc askvhighlight {row id} { + global hlview vhighlights commitrow iddrawn mainfont + + if {[info exists commitrow($hlview,$id)]} { + if {[info exists iddrawn($id)] && ![ishighlighted $row]} { + bolden $row [concat $mainfont bold] + } + set vhighlights($row) 1 + } else { + set vhighlights($row) 0 + } +} + +proc hfiles_change {name ix op} { + global highlight_files filehighlight fhighlights fh_serial + global mainfont highlight_paths + + if {[info exists filehighlight]} { + # delete previous highlights + catch {close $filehighlight} + unset filehighlight + catch {unset fhighlights} + unbolden + unhighlight_filelist + } + set highlight_paths {} + after cancel do_file_hl $fh_serial + incr fh_serial + if {$highlight_files ne {}} { + after 300 do_file_hl $fh_serial + } +} + +proc makepatterns {l} { + set ret {} + foreach e $l { + set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e] + if {[string index $ee end] eq "/"} { + lappend ret "$ee*" + } else { + lappend ret $ee + lappend ret "$ee/*" + } + } + return $ret +} + +proc do_file_hl {serial} { + global highlight_files filehighlight highlight_paths gdttype fhl_list + + if {$gdttype eq "touching paths:"} { + if {[catch {set paths [shellsplit $highlight_files]}]} return + set highlight_paths [makepatterns $paths] + highlight_filelist + set gdtargs [concat -- $paths] + } else { + set gdtargs [list "-S$highlight_files"] + } + set cmd [concat | git-diff-tree -r -s --stdin $gdtargs] + set filehighlight [open $cmd r+] + fconfigure $filehighlight -blocking 0 + fileevent $filehighlight readable readfhighlight + set fhl_list {} + drawvisible + flushhighlights +} + +proc flushhighlights {} { + global filehighlight fhl_list + + if {[info exists filehighlight]} { + lappend fhl_list {} + puts $filehighlight "" + flush $filehighlight + } +} + +proc askfilehighlight {row id} { + global filehighlight fhighlights fhl_list + + lappend fhl_list $id + set fhighlights($row) -1 + puts $filehighlight $id +} + +proc readfhighlight {} { + global filehighlight fhighlights commitrow curview mainfont iddrawn + global fhl_list + + while {[gets $filehighlight line] >= 0} { + set line [string trim $line] + set i [lsearch -exact $fhl_list $line] + if {$i < 0} continue + for {set j 0} {$j < $i} {incr j} { + set id [lindex $fhl_list $j] + if {[info exists commitrow($curview,$id)]} { + set fhighlights($commitrow($curview,$id)) 0 + } + } + set fhl_list [lrange $fhl_list [expr {$i+1}] end] + if {$line eq {}} continue + if {![info exists commitrow($curview,$line)]} continue + set row $commitrow($curview,$line) + if {[info exists iddrawn($line)] && ![ishighlighted $row]} { + bolden $row [concat $mainfont bold] + } + set fhighlights($row) 1 + } + if {[eof $filehighlight]} { + # strange... + puts "oops, git-diff-tree died" + catch {close $filehighlight} + unset filehighlight + } + next_hlcont +} + +proc find_change {name ix op} { + global nhighlights mainfont boldnamerows + global findstring findpattern findtype + + # delete previous highlights, if any + foreach row $boldnamerows { + bolden_name $row $mainfont + } + set boldnamerows {} + catch {unset nhighlights} + unbolden + if {$findtype ne "Regexp"} { + set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \ + $findstring] + set findpattern "*$e*" + } + drawvisible +} + +proc askfindhighlight {row id} { + global nhighlights commitinfo iddrawn mainfont + global findstring findtype findloc findpattern + + if {![info exists commitinfo($id)]} { + getcommit $id + } + set info $commitinfo($id) + set isbold 0 + set fldtypes {Headline Author Date Committer CDate Comments} + foreach f $info ty $fldtypes { + if {$findloc ne "All fields" && $findloc ne $ty} { + continue + } + if {$findtype eq "Regexp"} { + set doesmatch [regexp $findstring $f] + } elseif {$findtype eq "IgnCase"} { + set doesmatch [string match -nocase $findpattern $f] + } else { + set doesmatch [string match $findpattern $f] + } + if {$doesmatch} { + if {$ty eq "Author"} { + set isbold 2 + } else { + set isbold 1 + } + } + } + if {[info exists iddrawn($id)]} { + if {$isbold && ![ishighlighted $row]} { + bolden $row [concat $mainfont bold] + } + if {$isbold >= 2} { + bolden_name $row [concat $mainfont bold] + } + } + set nhighlights($row) $isbold +} + +proc vrel_change {name ix op} { + global highlight_related + + rhighlight_none + if {$highlight_related ne "None"} { + after idle drawvisible + } +} + +# prepare for testing whether commits are descendents or ancestors of a +proc rhighlight_sel {a} { + global descendent desc_todo ancestor anc_todo + global highlight_related rhighlights + + catch {unset descendent} + set desc_todo [list $a] + catch {unset ancestor} + set anc_todo [list $a] + if {$highlight_related ne "None"} { + rhighlight_none + after idle drawvisible + } +} + +proc rhighlight_none {} { + global rhighlights + + catch {unset rhighlights} + unbolden +} + +proc is_descendent {a} { + global curview children commitrow descendent desc_todo + + set v $curview + set la $commitrow($v,$a) + set todo $desc_todo + set leftover {} + set done 0 + for {set i 0} {$i < [llength $todo]} {incr i} { + set do [lindex $todo $i] + if {$commitrow($v,$do) < $la} { + lappend leftover $do + continue + } + foreach nk $children($v,$do) { + if {![info exists descendent($nk)]} { + set descendent($nk) 1 + lappend todo $nk + if {$nk eq $a} { + set done 1 + } + } + } + if {$done} { + set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]] + return + } + } + set descendent($a) 0 + set desc_todo $leftover +} + +proc is_ancestor {a} { + global curview parentlist commitrow ancestor anc_todo + + set v $curview + set la $commitrow($v,$a) + set todo $anc_todo + set leftover {} + set done 0 + for {set i 0} {$i < [llength $todo]} {incr i} { + set do [lindex $todo $i] + if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} { + lappend leftover $do + continue + } + foreach np [lindex $parentlist $commitrow($v,$do)] { + if {![info exists ancestor($np)]} { + set ancestor($np) 1 + lappend todo $np + if {$np eq $a} { + set done 1 + } + } + } + if {$done} { + set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]] + return + } + } + set ancestor($a) 0 + set anc_todo $leftover +} + +proc askrelhighlight {row id} { + global descendent highlight_related iddrawn mainfont rhighlights + global selectedline ancestor + + if {![info exists selectedline]} return + set isbold 0 + if {$highlight_related eq "Descendent" || + $highlight_related eq "Not descendent"} { + if {![info exists descendent($id)]} { + is_descendent $id + } + if {$descendent($id) == ($highlight_related eq "Descendent")} { + set isbold 1 + } + } elseif {$highlight_related eq "Ancestor" || + $highlight_related eq "Not ancestor"} { + if {![info exists ancestor($id)]} { + is_ancestor $id + } + if {$ancestor($id) == ($highlight_related eq "Ancestor")} { + set isbold 1 + } + } + if {[info exists iddrawn($id)]} { + if {$isbold && ![ishighlighted $row]} { + bolden $row [concat $mainfont bold] + } + } + set rhighlights($row) $isbold +} + +proc next_hlcont {} { + global fhl_row fhl_dirn displayorder numcommits + global vhighlights fhighlights nhighlights rhighlights + global hlview filehighlight findstring highlight_related + + if {![info exists fhl_dirn] || $fhl_dirn == 0} return + set row $fhl_row + while {1} { + if {$row < 0 || $row >= $numcommits} { + bell + set fhl_dirn 0 + return + } + set id [lindex $displayorder $row] + if {[info exists hlview]} { + if {![info exists vhighlights($row)]} { + askvhighlight $row $id + } + if {$vhighlights($row) > 0} break + } + if {$findstring ne {}} { + if {![info exists nhighlights($row)]} { + askfindhighlight $row $id + } + if {$nhighlights($row) > 0} break + } + if {$highlight_related ne "None"} { + if {![info exists rhighlights($row)]} { + askrelhighlight $row $id + } + if {$rhighlights($row) > 0} break + } + if {[info exists filehighlight]} { + if {![info exists fhighlights($row)]} { + # ask for a few more while we're at it... + set r $row + for {set n 0} {$n < 100} {incr n} { + if {![info exists fhighlights($r)]} { + askfilehighlight $r [lindex $displayorder $r] + } + incr r $fhl_dirn + if {$r < 0 || $r >= $numcommits} break } + flushhighlights + } + if {$fhighlights($row) < 0} { + set fhl_row $row + return } + if {$fhighlights($row) > 0} break } + incr row $fhl_dirn } - set highlighted($hlview) $max + set fhl_dirn 0 + selectline $row 1 +} + +proc next_highlight {dirn} { + global selectedline fhl_row fhl_dirn + global hlview filehighlight findstring highlight_related + + if {![info exists selectedline]} return + if {!([info exists hlview] || $findstring ne {} || + $highlight_related ne "None" || [info exists filehighlight])} return + set fhl_row [expr {$selectedline + $dirn}] + set fhl_dirn $dirn + next_hlcont +} + +proc cancel_next_highlight {} { + global fhl_dirn + + set fhl_dirn 0 } # Graph layout functions @@ -2336,8 +2873,7 @@ proc drawcmittext {id row col rmx} { global commitlisted commitinfo rowidlist global rowtextx idpos idtags idheads idotherrefs global linehtag linentag linedtag - global mainfont canvxmax - global hlview commitrow highlightedrows + global mainfont canvxmax boldrows boldnamerows set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}] set x [xc $row $col] @@ -2363,15 +2899,21 @@ proc drawcmittext {id row col rmx} { set date [lindex $commitinfo($id) 2] set date [formatdate $date] set font $mainfont - if {[info exists hlview] && [info exists commitrow($hlview,$id)]} { + set nfont $mainfont + set isbold [ishighlighted $row] + if {$isbold > 0} { + lappend boldrows $row lappend font bold - lappend highlightedrows $row + if {$isbold > 1} { + lappend boldnamerows $row + lappend nfont bold + } } set linehtag($row) [$canv create text $xt $y -anchor w \ -text $headline -font $font] $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id" set linentag($row) [$canv2 create text 3 $y -anchor w \ - -text $name -font $mainfont] + -text $name -font $nfont] set linedtag($row) [$canv3 create text 3 $y -anchor w \ -text $date -font $mainfont] set xr [expr {$xt + [font measure $mainfont $headline]}] @@ -2385,6 +2927,9 @@ proc drawcmitrow {row} { global displayorder rowidlist global idrangedrawn iddrawn global commitinfo parentlist numcommits + global filehighlight fhighlights findstring nhighlights + global hlview vhighlights + global highlight_related rhighlights if {$row >= $numcommits} return foreach id [lindex $rowidlist $row] { @@ -2405,6 +2950,18 @@ proc drawcmitrow {row} { } set id [lindex $displayorder $row] + if {[info exists hlview] && ![info exists vhighlights($row)]} { + askvhighlight $row $id + } + if {[info exists filehighlight] && ![info exists fhighlights($row)]} { + askfilehighlight $row $id + } + if {$findstring ne {} && ![info exists nhighlights($row)]} { + askfindhighlight $row $id + } + if {$highlight_related ne "None" && ![info exists rhighlights($row)]} { + askrelhighlight $row $id + } if {[info exists iddrawn($id)]} return set col [lsearch -exact [lindex $rowidlist $row] $id] if {$col < 0} { @@ -2453,10 +3010,15 @@ proc drawvisible {} { proc clear_display {} { global iddrawn idrangedrawn + global vhighlights fhighlights nhighlights rhighlights allcanvs delete all catch {unset iddrawn} catch {unset idrangedrawn} + catch {unset vhighlights} + catch {unset fhighlights} + catch {unset nhighlights} + catch {unset rhighlights} } proc findcrossings {id} { @@ -2760,12 +3322,9 @@ proc dofind {} { stopfindproc unmarkmatches + cancel_next_highlight focus . set matchinglines {} - if {$findloc == "Pickaxe"} { - findpatches - return - } if {$findtype == "IgnCase"} { set foundstring [string tolower $findstring] } else { @@ -2775,17 +3334,13 @@ proc dofind {} { if {$foundstrlen == 0} return regsub -all {[*?\[\\]} $foundstring {\\&} matchstring set matchstring "*$matchstring*" - if {$findloc == "Files"} { - findfiles - return - } if {![info exists selectedline]} { set oldsel -1 } else { set oldsel $selectedline } set didsel 0 - set fldtypes {Headline Author Date Committer CDate Comment} + set fldtypes {Headline Author Date Committer CDate Comments} set l -1 foreach id $displayorder { set d $commitdata($id) @@ -2888,18 +3443,6 @@ proc findprev {} { } } -proc findlocchange {name ix op} { - global findloc findtype findtypemenu - if {$findloc == "Pickaxe"} { - set findtype Exact - set state disabled - } else { - set state normal - } - $findtypemenu entryconf 1 -state $state - $findtypemenu entryconf 2 -state $state -} - proc stopfindproc {{done 0}} { global findprocpid findprocfile findids global ctext findoldcursor phase maincursor textcursor @@ -2917,247 +3460,6 @@ proc stopfindproc {{done 0}} { notbusy find } -proc findpatches {} { - global findstring selectedline numcommits - global findprocpid findprocfile - global finddidsel ctext displayorder findinprogress - global findinsertpos - - if {$numcommits == 0} return - - # make a list of all the ids to search, starting at the one - # after the selected line (if any) - if {[info exists selectedline]} { - set l $selectedline - } else { - set l -1 - } - set inputids {} - for {set i 0} {$i < $numcommits} {incr i} { - if {[incr l] >= $numcommits} { - set l 0 - } - append inputids [lindex $displayorder $l] "\n" - } - - if {[catch { - set f [open [list | git diff-tree --stdin -s -r -S$findstring \ - << $inputids] r] - } err]} { - error_popup "Error starting search process: $err" - return - } - - set findinsertpos end - set findprocfile $f - set findprocpid [pid $f] - fconfigure $f -blocking 0 - fileevent $f readable readfindproc - set finddidsel 0 - nowbusy find - set findinprogress 1 -} - -proc readfindproc {} { - global findprocfile finddidsel - global commitrow matchinglines findinsertpos curview - - set n [gets $findprocfile line] - if {$n < 0} { - if {[eof $findprocfile]} { - stopfindproc 1 - if {!$finddidsel} { - bell - } - } - return - } - if {![regexp {^[0-9a-f]{40}} $line id]} { - error_popup "Can't parse git diff-tree output: $line" - stopfindproc - return - } - if {![info exists commitrow($curview,$id)]} { - puts stderr "spurious id: $id" - return - } - set l $commitrow($curview,$id) - insertmatch $l $id -} - -proc insertmatch {l id} { - global matchinglines findinsertpos finddidsel - - if {$findinsertpos == "end"} { - if {$matchinglines != {} && $l < [lindex $matchinglines 0]} { - set matchinglines [linsert $matchinglines 0 $l] - set findinsertpos 1 - } else { - lappend matchinglines $l - } - } else { - set matchinglines [linsert $matchinglines $findinsertpos $l] - incr findinsertpos - } - markheadline $l $id - if {!$finddidsel} { - findselectline $l - set finddidsel 1 - } -} - -proc findfiles {} { - global selectedline numcommits displayorder ctext - global ffileline finddidsel parentlist - global findinprogress findstartline findinsertpos - global treediffs fdiffid fdiffsneeded fdiffpos - global findmergefiles - - if {$numcommits == 0} return - - if {[info exists selectedline]} { - set l [expr {$selectedline + 1}] - } else { - set l 0 - } - set ffileline $l - set findstartline $l - set diffsneeded {} - set fdiffsneeded {} - while 1 { - set id [lindex $displayorder $l] - if {$findmergefiles || [llength [lindex $parentlist $l]] == 1} { - if {![info exists treediffs($id)]} { - append diffsneeded "$id\n" - lappend fdiffsneeded $id - } - } - if {[incr l] >= $numcommits} { - set l 0 - } - if {$l == $findstartline} break - } - - # start off a git diff-tree process if needed - if {$diffsneeded ne {}} { - if {[catch { - set df [open [list | git diff-tree -r --stdin << $diffsneeded] r] - } err ]} { - error_popup "Error starting search process: $err" - return - } - catch {unset fdiffid} - set fdiffpos 0 - fconfigure $df -blocking 0 - fileevent $df readable [list readfilediffs $df] - } - - set finddidsel 0 - set findinsertpos end - set id [lindex $displayorder $l] - nowbusy find - set findinprogress 1 - findcont - update -} - -proc readfilediffs {df} { - global findid fdiffid fdiffs - - set n [gets $df line] - if {$n < 0} { - if {[eof $df]} { - donefilediff - if {[catch {close $df} err]} { - stopfindproc - bell - error_popup "Error in git diff-tree: $err" - } elseif {[info exists findid]} { - set id $findid - stopfindproc - bell - error_popup "Couldn't find diffs for $id" - } - } - return - } - if {[regexp {^([0-9a-f]{40})$} $line match id]} { - # start of a new string of diffs - donefilediff - set fdiffid $id - set fdiffs {} - } elseif {[string match ":*" $line]} { - lappend fdiffs [lindex $line 5] - } -} - -proc donefilediff {} { - global fdiffid fdiffs treediffs findid - global fdiffsneeded fdiffpos - - if {[info exists fdiffid]} { - while {[lindex $fdiffsneeded $fdiffpos] ne $fdiffid - && $fdiffpos < [llength $fdiffsneeded]} { - # git diff-tree doesn't output anything for a commit - # which doesn't change anything - set nullid [lindex $fdiffsneeded $fdiffpos] - set treediffs($nullid) {} - if {[info exists findid] && $nullid eq $findid} { - unset findid - findcont - } - incr fdiffpos - } - incr fdiffpos - - if {![info exists treediffs($fdiffid)]} { - set treediffs($fdiffid) $fdiffs - } - if {[info exists findid] && $fdiffid eq $findid} { - unset findid - findcont - } - } -} - -proc findcont {} { - global findid treediffs parentlist - global ffileline findstartline finddidsel - global displayorder numcommits matchinglines findinprogress - global findmergefiles - - set l $ffileline - while {1} { - set id [lindex $displayorder $l] - if {$findmergefiles || [llength [lindex $parentlist $l]] == 1} { - if {![info exists treediffs($id)]} { - set findid $id - set ffileline $l - return - } - set doesmatch 0 - foreach f $treediffs($id) { - set x [findmatches $f] - if {$x != {}} { - set doesmatch 1 - break - } - } - if {$doesmatch} { - insertmatch $l $id - } - } - if {[incr l] >= $numcommits} { - set l 0 - } - if {$l == $findstartline} break - } - stopfindproc - if {!$finddidsel} { - bell - } -} - # mark a commit as matching by putting a yellow background # behind the headline proc markheadline {l id} { @@ -3222,7 +3524,7 @@ proc commit_descriptor {p} { if {[llength $commitinfo($p)] > 1} { set l [lindex $commitinfo($p) 0] } - return "$p ($l)" + return "$p ($l)\n" } # append some text to the ctext widget, and make any SHA1 ID @@ -3232,7 +3534,6 @@ proc appendwithlinks {text tags} { set start [$ctext index "end - 1c"] $ctext insert end $text $tags - $ctext insert end "\n" set links [regexp -indices -all -inline {[0-9a-f]{40}} $text] foreach l $links { set s [lindex $l 0] @@ -3267,6 +3568,64 @@ proc viewnextline {dir} { allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}] } +# add a list of tag or branch names at position pos +# returns the number of names inserted +proc appendrefs {pos l var} { + global ctext commitrow linknum curview idtags $var + + if {[catch {$ctext index $pos}]} { + return 0 + } + set tags {} + foreach id $l { + foreach tag [set $var\($id\)] { + lappend tags [concat $tag $id] + } + } + set tags [lsort -index 1 $tags] + set sep {} + foreach tag $tags { + set name [lindex $tag 0] + set id [lindex $tag 1] + set lk link$linknum + incr linknum + $ctext insert $pos $sep + $ctext insert $pos $name $lk + $ctext tag conf $lk -foreground blue + if {[info exists commitrow($curview,$id)]} { + $ctext tag bind $lk <1> \ + [list selectline $commitrow($curview,$id) 1] + $ctext tag conf $lk -underline 1 + $ctext tag bind $lk <Enter> { %W configure -cursor hand2 } + $ctext tag bind $lk <Leave> { %W configure -cursor $curtextcursor } + } + set sep ", " + } + return [llength $tags] +} + +# called when we have finished computing the nearby tags +proc dispneartags {} { + global selectedline currentid ctext anc_tags desc_tags showneartags + global desc_heads + + if {![info exists selectedline] || !$showneartags} return + set id $currentid + $ctext conf -state normal + if {[info exists desc_heads($id)]} { + if {[appendrefs branch $desc_heads($id) idheads] > 1} { + $ctext insert "branch -2c" "es" + } + } + if {[info exists anc_tags($id)]} { + appendrefs follows $anc_tags($id) idtags + } + if {[info exists desc_tags($id)]} { + appendrefs precedes $desc_tags($id) idtags + } + $ctext conf -state disabled +} + proc selectline {l isnew} { global canv canv2 canv3 ctext commitinfo selectedline global displayorder linehtag linentag linedtag @@ -3274,11 +3633,12 @@ proc selectline {l isnew} { global currentid sha1entry global commentend idtags linknum global mergemax numcommits pending_select - global cmitmode + global cmitmode desc_tags anc_tags showneartags allcommits desc_heads catch {unset pending_select} $canv delete hover normalline + cancel_next_highlight if {$l < 0 || $l >= $numcommits} return set y [expr {$canvy0 + $l * $linespc}] set ymax [lindex [$canv cget -scrollregion] 3] @@ -3342,9 +3702,10 @@ proc selectline {l isnew} { $sha1entry insert 0 $id $sha1entry selection from 0 $sha1entry selection to end + rhighlight_sel $id $ctext conf -state normal - $ctext delete 0.0 end + clear_ctext set linknum 0 set info $commitinfo($id) set date [formatdate [lindex $info 2]] @@ -3375,16 +3736,44 @@ proc selectline {l isnew} { } } else { foreach p $olds { - append headers "Parent: [commit_descriptor $p]\n" + append headers "Parent: [commit_descriptor $p]" } } foreach c [lindex $childlist $l] { - append headers "Child: [commit_descriptor $c]\n" + append headers "Child: [commit_descriptor $c]" } # make anything that looks like a SHA1 ID be a clickable link appendwithlinks $headers {} + if {$showneartags} { + if {![info exists allcommits]} { + getallcommits + } + $ctext insert end "Branch: " + $ctext mark set branch "end -1c" + $ctext mark gravity branch left + if {[info exists desc_heads($id)]} { + if {[appendrefs branch $desc_heads($id) idheads] > 1} { + # turn "Branch" into "Branches" + $ctext insert "branch -2c" "es" + } + } + $ctext insert end "\nFollows: " + $ctext mark set follows "end -1c" + $ctext mark gravity follows left + if {[info exists anc_tags($id)]} { + appendrefs follows $anc_tags($id) idtags + } + $ctext insert end "\nPrecedes: " + $ctext mark set precedes "end -1c" + $ctext mark gravity precedes left + if {[info exists desc_tags($id)]} { + appendrefs precedes $desc_tags($id) idtags + } + $ctext insert end "\n" + } + $ctext insert end "\n" appendwithlinks [lindex $info 5] {comment} $ctext tag delete Comments @@ -3448,6 +3837,8 @@ proc unselectline {} { catch {unset selectedline} catch {unset currentid} allcanvs delete secsel + rhighlight_none + cancel_next_highlight } proc reselectline {} { @@ -3581,7 +3972,7 @@ proc showfile {f} { fconfigure $bf -blocking 0 fileevent $bf readable [list getblobline $bf $diffids] $ctext config -state normal - $ctext delete $commentend end + clear_ctext $commentend $ctext insert end "\n" $ctext insert end "$f\n" filesep $ctext config -state disabled @@ -3881,6 +4272,138 @@ proc nextfile {} { } } +proc clear_ctext {{first 1.0}} { + global ctext smarktop smarkbot + + set l [lindex [split $first .] 0] + if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} { + set smarktop $l + } + if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.0]} { + set smarkbot $l + } + $ctext delete $first end +} + +proc incrsearch {name ix op} { + global ctext searchstring searchdirn + + $ctext tag remove found 1.0 end + if {[catch {$ctext index anchor}]} { + # no anchor set, use start of selection, or of visible area + set sel [$ctext tag ranges sel] + if {$sel ne {}} { + $ctext mark set anchor [lindex $sel 0] + } elseif {$searchdirn eq "-forwards"} { + $ctext mark set anchor @0,0 + } else { + $ctext mark set anchor @0,[winfo height $ctext] + } + } + if {$searchstring ne {}} { + set here [$ctext search $searchdirn -- $searchstring anchor] + if {$here ne {}} { + $ctext see $here + } + searchmarkvisible 1 + } +} + +proc dosearch {} { + global sstring ctext searchstring searchdirn + + focus $sstring + $sstring icursor end + set searchdirn -forwards + if {$searchstring ne {}} { + set sel [$ctext tag ranges sel] + if {$sel ne {}} { + set start "[lindex $sel 0] + 1c" + } elseif {[catch {set start [$ctext index anchor]}]} { + set start "@0,0" + } + set match [$ctext search -count mlen -- $searchstring $start] + $ctext tag remove sel 1.0 end + if {$match eq {}} { + bell + return + } + $ctext see $match + set mend "$match + $mlen c" + $ctext tag add sel $match $mend + $ctext mark unset anchor + } +} + +proc dosearchback {} { + global sstring ctext searchstring searchdirn + + focus $sstring + $sstring icursor end + set searchdirn -backwards + if {$searchstring ne {}} { + set sel [$ctext tag ranges sel] + if {$sel ne {}} { + set start [lindex $sel 0] + } elseif {[catch {set start [$ctext index anchor]}]} { + set start @0,[winfo height $ctext] + } + set match [$ctext search -backwards -count ml -- $searchstring $start] + $ctext tag remove sel 1.0 end + if {$match eq {}} { + bell + return + } + $ctext see $match + set mend "$match + $ml c" + $ctext tag add sel $match $mend + $ctext mark unset anchor + } +} + +proc searchmark {first last} { + global ctext searchstring + + set mend $first.0 + while {1} { + set match [$ctext search -count mlen -- $searchstring $mend $last.end] + if {$match eq {}} break + set mend "$match + $mlen c" + $ctext tag add found $match $mend + } +} + +proc searchmarkvisible {doall} { + global ctext smarktop smarkbot + + set topline [lindex [split [$ctext index @0,0] .] 0] + set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0] + if {$doall || $botline < $smarktop || $topline > $smarkbot} { + # no overlap with previous + searchmark $topline $botline + set smarktop $topline + set smarkbot $botline + } else { + if {$topline < $smarktop} { + searchmark $topline [expr {$smarktop-1}] + set smarktop $topline + } + if {$botline > $smarkbot} { + searchmark [expr {$smarkbot+1}] $botline + set smarkbot $botline + } + } +} + +proc scrolltext {f0 f1} { + global searchstring + + .ctop.cdet.left.sb set $f0 $f1 + if {$searchstring ne {}} { + searchmarkvisible 0 + } +} + proc setcoords {} { global linespc charspc canvx0 canvy0 mainfont global xspc1 xspc2 lthickness @@ -4115,7 +4638,7 @@ proc lineclick {x y id isnew} { } # fill the details pane with info about this line $ctext conf -state normal - $ctext delete 0.0 end + clear_ctext $ctext tag conf link -foreground blue -underline 1 $ctext tag bind link <Enter> { %W configure -cursor hand2 } $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor } @@ -4208,7 +4731,7 @@ proc doseldiff {oldid newid} { global commitinfo $ctext conf -state normal - $ctext delete 0.0 end + clear_ctext init_flist "Top" $ctext insert end "From " $ctext tag conf link -foreground blue -underline 1 @@ -4377,12 +4900,19 @@ proc domktag {} { proc redrawtags {id} { global canv linehtag commitrow idpos selectedline curview + global mainfont if {![info exists commitrow($curview,$id)]} return drawcmitrow $commitrow($curview,$id) $canv delete tag.$id set xt [eval drawtags $id $idpos($id)] $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2] + set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text] + set xr [expr {$xt + [font measure $mainfont $text]}] + if {$xr > $canvxmax} { + set canvxmax $xr + setcanvscroll + } if {[info exists selectedline] && $selectedline == $commitrow($curview,$id)} { selectline $selectedline 0 @@ -4456,22 +4986,177 @@ proc wrcomcan {} { unset wrcomtop } -proc listrefs {id} { - global idtags idheads idotherrefs +# Stuff for finding nearby tags +proc getallcommits {} { + global allcstart allcommits - set x {} - if {[info exists idtags($id)]} { - set x $idtags($id) + set fd [open [concat | git rev-list --all --topo-order --parents] r] + fconfigure $fd -blocking 0 + set allcommits "reading" + nowbusy allcommits + restartgetall $fd +} + +proc restartgetall {fd} { + global allcstart + + fileevent $fd readable [list getallclines $fd] + set allcstart [clock clicks -milliseconds] +} + +proc combine_dtags {l1 l2} { + global tagisdesc notfirstd + + set res [lsort -unique [concat $l1 $l2]] + for {set i 0} {$i < [llength $res]} {incr i} { + set x [lindex $res $i] + for {set j [expr {$i+1}]} {$j < [llength $res]} {} { + set y [lindex $res $j] + if {[info exists tagisdesc($x,$y)]} { + if {$tagisdesc($x,$y) > 0} { + # x is a descendent of y, exclude x + set res [lreplace $res $i $i] + incr i -1 + break + } else { + # y is a descendent of x, exclude y + set res [lreplace $res $j $j] + } + } else { + # no relation, keep going + incr j + } + } } - set y {} - if {[info exists idheads($id)]} { - set y $idheads($id) + return $res +} + +proc combine_atags {l1 l2} { + global tagisdesc + + set res [lsort -unique [concat $l1 $l2]] + for {set i 0} {$i < [llength $res]} {incr i} { + set x [lindex $res $i] + for {set j [expr {$i+1}]} {$j < [llength $res]} {} { + set y [lindex $res $j] + if {[info exists tagisdesc($x,$y)]} { + if {$tagisdesc($x,$y) < 0} { + # x is an ancestor of y, exclude x + set res [lreplace $res $i $i] + incr i -1 + break + } else { + # y is an ancestor of x, exclude y + set res [lreplace $res $j $j] + } + } else { + # no relation, keep going + incr j + } + } } - set z {} - if {[info exists idotherrefs($id)]} { - set z $idotherrefs($id) + return $res +} + +proc getallclines {fd} { + global allparents allchildren allcommits allcstart + global desc_tags anc_tags idtags alldtags tagisdesc allids + global desc_heads idheads + + while {[gets $fd line] >= 0} { + set id [lindex $line 0] + lappend allids $id + set olds [lrange $line 1 end] + set allparents($id) $olds + if {![info exists allchildren($id)]} { + set allchildren($id) {} + } + foreach p $olds { + lappend allchildren($p) $id + } + # compute nearest tagged descendents as we go + # also compute descendent heads + set dtags {} + set dheads {} + foreach child $allchildren($id) { + if {[info exists idtags($child)]} { + set ctags [list $child] + } else { + set ctags $desc_tags($child) + } + if {$dtags eq {}} { + set dtags $ctags + } elseif {$ctags ne $dtags} { + set dtags [combine_dtags $dtags $ctags] + } + set cheads $desc_heads($child) + if {$dheads eq {}} { + set dheads $cheads + } elseif {$cheads ne $dheads} { + set dheads [lsort -unique [concat $dheads $cheads]] + } + } + set desc_tags($id) $dtags + if {[info exists idtags($id)]} { + set adt $dtags + foreach tag $dtags { + set adt [concat $adt $alldtags($tag)] + } + set adt [lsort -unique $adt] + set alldtags($id) $adt + foreach tag $adt { + set tagisdesc($id,$tag) -1 + set tagisdesc($tag,$id) 1 + } + } + if {[info exists idheads($id)]} { + lappend dheads $id + } + set desc_heads($id) $dheads + if {[clock clicks -milliseconds] - $allcstart >= 50} { + fileevent $fd readable {} + after idle restartgetall $fd + return + } + } + if {[eof $fd]} { + after idle restartatags [llength $allids] + if {[catch {close $fd} err]} { + error_popup "Error reading full commit graph: $err.\n\ + Results may be incomplete." + } } - return [list $x $y $z] +} + +# walk backward through the tree and compute nearest tagged ancestors +proc restartatags {i} { + global allids allparents idtags anc_tags t0 + + set t0 [clock clicks -milliseconds] + while {[incr i -1] >= 0} { + set id [lindex $allids $i] + set atags {} + foreach p $allparents($id) { + if {[info exists idtags($p)]} { + set ptags [list $p] + } else { + set ptags $anc_tags($p) + } + if {$atags eq {}} { + set atags $ptags + } elseif {$ptags ne $atags} { + set atags [combine_atags $atags $ptags] + } + } + set anc_tags($id) $atags + if {[clock clicks -milliseconds] - $t0 >= 50} { + after idle restartatags $i + return + } + } + set allcommits "done" + notbusy allcommits + dispneartags } proc rereadrefs {} { @@ -4502,7 +5187,7 @@ proc showtag {tag isnew} { addtohistory [list showtag $tag 0] } $ctext conf -state normal - $ctext delete 0.0 end + clear_ctext set linknum 0 if {[info exists tagcontents($tag)]} { set text $tagcontents($tag) @@ -4521,8 +5206,8 @@ proc doquit {} { } proc doprefs {} { - global maxwidth maxgraphpct diffopts findmergefiles - global oldprefs prefstop + global maxwidth maxgraphpct diffopts + global oldprefs prefstop showneartags set top .gitkprefs set prefstop $top @@ -4530,7 +5215,7 @@ proc doprefs {} { raise $top return } - foreach v {maxwidth maxgraphpct diffopts findmergefiles} { + foreach v {maxwidth maxgraphpct diffopts showneartags} { set oldprefs($v) [set $v] } toplevel $top @@ -4546,16 +5231,17 @@ proc doprefs {} { -font optionfont spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct grid x $top.maxpctl $top.maxpct -sticky w - checkbutton $top.findm -variable findmergefiles - label $top.findml -text "Include merges for \"Find\" in \"Files\"" \ - -font optionfont - grid $top.findm $top.findml - -sticky w label $top.ddisp -text "Diff display options" grid $top.ddisp - -sticky w -pady 10 label $top.diffoptl -text "Options for diff program" \ -font optionfont entry $top.diffopt -width 20 -textvariable diffopts grid x $top.diffoptl $top.diffopt -sticky w + frame $top.ntag + label $top.ntag.l -text "Display nearby tags" -font optionfont + checkbutton $top.ntag.b -variable showneartags + pack $top.ntag.b $top.ntag.l -side left + grid x $top.ntag -sticky w frame $top.buts button $top.buts.ok -text "OK" -command prefsok button $top.buts.can -text "Cancel" -command prefscan @@ -4566,10 +5252,10 @@ proc doprefs {} { } proc prefscan {} { - global maxwidth maxgraphpct diffopts findmergefiles - global oldprefs prefstop + global maxwidth maxgraphpct diffopts + global oldprefs prefstop showneartags - foreach v {maxwidth maxgraphpct diffopts findmergefiles} { + foreach v {maxwidth maxgraphpct diffopts showneartags} { set $v $oldprefs($v) } catch {destroy $prefstop} @@ -4578,13 +5264,15 @@ proc prefscan {} { proc prefsok {} { global maxwidth maxgraphpct - global oldprefs prefstop + global oldprefs prefstop showneartags catch {destroy $prefstop} unset prefstop if {$maxwidth != $oldprefs(maxwidth) || $maxgraphpct != $oldprefs(maxgraphpct)} { redisplay + } elseif {$showneartags != $oldprefs(showneartags)} { + reselectline } } @@ -4893,9 +5581,9 @@ set fastdate 0 set uparrowlen 7 set downarrowlen 7 set mingaplen 30 -set flistmode "flat" set cmitmode "patch" set wrapcomment "none" +set showneartags 1 set colors {green red blue magenta darkgrey brown orange} @@ -4946,13 +5634,19 @@ if {$i >= 0} { set history {} set historyindex 0 +set fh_serial 0 +set nhl_names {} +set highlight_paths {} +set searchdirn -forwards +set boldrows {} +set boldnamerows {} set optim_delay 16 set nextviewnum 1 set curview 0 set selectedview 0 -set selectedhlview {} +set selectedhlview None set viewfiles(0) {} set viewperm(0) 0 set viewargs(0) {} diff --git a/pack-objects.c b/pack-objects.c index 3590cd5e59..179560f2bd 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -463,48 +463,21 @@ static void rehash_objects(void) } } -struct name_path { - struct name_path *up; - const char *elem; - int len; -}; - -#define DIRBITS 12 - -static unsigned name_hash(struct name_path *path, const char *name) +static unsigned name_hash(const char *name) { - struct name_path *p = path; - const char *n = name + strlen(name); - unsigned hash = 0, name_hash = 0, name_done = 0; - - if (n != name && n[-1] == '\n') - n--; - while (name <= --n) { - unsigned char c = *n; - if (c == '/' && !name_done) { - name_hash = hash; - name_done = 1; - hash = 0; - } - hash = hash * 11 + c; - } - if (!name_done) { - name_hash = hash; - hash = 0; - } - for (p = path; p; p = p->up) { - hash = hash * 11 + '/'; - n = p->elem + p->len; - while (p->elem <= --n) { - unsigned char c = *n; - hash = hash * 11 + c; - } - } + unsigned char c; + unsigned hash = 0; + /* - * Make sure "Makefile" and "t/Makefile" are hashed separately - * but close enough. + * This effectively just creates a sortable number from the + * last sixteen non-whitespace characters. Last characters + * count "most", so things that end in ".c" sort together. */ - hash = (name_hash<<DIRBITS) | (hash & ((1U<<DIRBITS )-1)); + while ((c = *name++) != 0) { + if (isspace(c)) + continue; + hash = (hash >> 2) + (c << 24); + } return hash; } @@ -686,9 +659,9 @@ static int name_cmp_len(const char *name) } static void add_pbase_object(struct tree_desc *tree, - struct name_path *up, const char *name, - int cmplen) + int cmplen, + const char *fullname) { struct name_entry entry; @@ -702,13 +675,12 @@ static void add_pbase_object(struct tree_desc *tree, sha1_object_info(entry.sha1, type, &size)) continue; if (name[cmplen] != '/') { - unsigned hash = name_hash(up, name); + unsigned hash = name_hash(fullname); add_object_entry(entry.sha1, hash, 1); return; } if (!strcmp(type, tree_type)) { struct tree_desc sub; - struct name_path me; struct pbase_tree_cache *tree; const char *down = name+cmplen+1; int downlen = name_cmp_len(down); @@ -719,10 +691,7 @@ static void add_pbase_object(struct tree_desc *tree, sub.buf = tree->tree_data; sub.size = tree->tree_size; - me.up = up; - me.elem = entry.path; - me.len = entry.pathlen; - add_pbase_object(&sub, &me, down, downlen); + add_pbase_object(&sub, down, downlen, fullname); pbase_tree_put(tree); } } @@ -778,14 +747,14 @@ static void add_preferred_base_object(char *name, unsigned hash) for (it = pbase_tree; it; it = it->next) { if (cmplen == 0) { - hash = name_hash(NULL, ""); + hash = name_hash(""); add_object_entry(it->pcache.sha1, hash, 1); } else { struct tree_desc tree; tree.buf = it->pcache.tree_data; tree.size = it->pcache.tree_size; - add_pbase_object(&tree, NULL, name, cmplen); + add_pbase_object(&tree, name, cmplen, name); } } } @@ -1328,7 +1297,7 @@ int main(int argc, char **argv) } if (get_sha1_hex(line, sha1)) die("expected sha1, got garbage:\n %s", line); - hash = name_hash(NULL, line+41); + hash = name_hash(line+41); add_preferred_base_object(line+41, hash); add_object_entry(sha1, hash, 0); } diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index cf33989b56..2c9bbb59b0 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -195,6 +195,20 @@ test_expect_success \ 'git-ls-tree -r output for a known tree.' \ 'diff current expected' +test_expect_success \ + 'writing partial tree out with git-write-tree --prefix.' \ + 'ptree=$(git-write-tree --prefix=path3)' +test_expect_success \ + 'validate object ID for a known tree.' \ + 'test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3' + +test_expect_success \ + 'writing partial tree out with git-write-tree --prefix.' \ + 'ptree=$(git-write-tree --prefix=path3/subp3)' +test_expect_success \ + 'validate object ID for a known tree.' \ + 'test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2' + ################################################################ rm .git/index test_expect_success \ diff --git a/write-tree.c b/write-tree.c index 7a4f691d8a..895e7a359d 100644 --- a/write-tree.c +++ b/write-tree.c @@ -8,8 +8,10 @@ #include "cache-tree.h" static int missing_ok = 0; +static char *prefix = NULL; -static const char write_tree_usage[] = "git-write-tree [--missing-ok]"; +static const char write_tree_usage[] = +"git-write-tree [--missing-ok] [--prefix=<prefix>/]"; static struct cache_file cache_file; @@ -21,13 +23,18 @@ int main(int argc, char **argv) newfd = hold_index_file_for_update(&cache_file, get_index_file()); entries = read_cache(); - if (argc == 2) { - if (!strcmp(argv[1], "--missing-ok")) + + while (1 < argc) { + char *arg = argv[1]; + if (!strcmp(arg, "--missing-ok")) missing_ok = 1; + else if (!strncmp(arg, "--prefix=", 9)) + prefix = arg + 9; else die(write_tree_usage); + argc--; argv++; } - + if (argc > 2) die("too many options"); @@ -54,6 +61,12 @@ int main(int argc, char **argv) * performance penalty and not a big deal. */ } - printf("%s\n", sha1_to_hex(active_cache_tree->sha1)); + if (prefix) { + struct cache_tree *subtree = + cache_tree_find(active_cache_tree, prefix); + printf("%s\n", sha1_to_hex(subtree->sha1)); + } + else + printf("%s\n", sha1_to_hex(active_cache_tree->sha1)); return 0; } |