* [RFC] Making ce_path_match() more useful by accepting globs
From: Junio C Hamano @ 2007-11-25 18:03 UTC (permalink / raw)
To: Linus, "Torvalds <torvalds"; +Cc: git
Currently ce_path_match() only uses "the leading directory" match, and
does not understand file globs. These do not work:
git diff-files 't/*.sh'
git diff-index HEAD 'xdiff/*.c'
git update-index -g 'Documentation/howto/*.txt'
This teaches the ce_path_match(), the underlying function that are used
for checking if a given cache entry matches the given set of pathspecs,
to use the match_pathspec() from git-ls-files, which knows about glob
patterns.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
* Having two different behaviours of pathspec matching has been
bothering me for quite some time. The changes here look trivially
correct and the result passes all the tests, but this is quite close
to the core part of the system, and would benefit greatly from extra
set of eyes.
This patch does not touch the tree walker, and does not affect
diff-tree nor ancestry pruning done in the revision traversal. That
however is even closer to the core and is performance critical. It
needs to be done carefully not to descend into trees that would never
match needlessly. IOW, not today.
dir.c | 5 +++--
read-cache.c | 17 ++---------------
2 files changed, 5 insertions(+), 17 deletions(-)
diff --git a/dir.c b/dir.c
index 225fdfb..be640c9 100644
--- a/dir.c
+++ b/dir.c
@@ -98,20 +98,21 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, int pre
{
int retval;
const char *match;
+ int want_seen = !!seen;
name += prefix;
namelen -= prefix;
for (retval = 0; (match = *pathspec++) != NULL; seen++) {
int how;
- if (retval && *seen == MATCHED_EXACTLY)
+ if (retval && seen && want_seen && *seen == MATCHED_EXACTLY)
continue;
match += prefix;
how = match_one(match, name, namelen);
if (how) {
if (retval < how)
retval = how;
- if (*seen < how)
+ if (want_seen && *seen < how)
*seen = how;
}
}
diff --git a/read-cache.c b/read-cache.c
index 7db5588..767464e 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -472,7 +472,7 @@ int ce_same_name(struct cache_entry *a, struct cache_entry *b)
int ce_path_match(const struct cache_entry *ce, const char **pathspec)
{
- const char *match, *name;
+ const char *name;
int len;
if (!pathspec)
@@ -480,20 +480,7 @@ int ce_path_match(const struct cache_entry *ce, const char **pathspec)
len = ce_namelen(ce);
name = ce->name;
- while ((match = *pathspec++) != NULL) {
- int matchlen = strlen(match);
- if (matchlen > len)
- continue;
- if (memcmp(name, match, matchlen))
- continue;
- if (matchlen && name[matchlen-1] == '/')
- return 1;
- if (name[matchlen] == '/' || !name[matchlen])
- return 1;
- if (!matchlen)
- return 1;
- }
- return 0;
+ return !!match_pathspec(pathspec, name, len, 0, NULL);
}
/*
^ permalink raw reply related
* [PATCH] builtin-add: fix command line building to call interactive
From: Junio C Hamano @ 2007-11-25 18:07 UTC (permalink / raw)
To: Wincent Colaiuta; +Cc: git, peff
In-Reply-To: <1195996542-86074-1-git-send-email-win@wincent.com>
The earlier 7c0ab4458994aa895855abc4a504cf693ecc0cf1 (Teach builtin-add
to pass multiple paths to git-add--interactive) did not allocate enough,
and had unneeded (void*) pointer arithmetic.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
builtin-add.c | 5 +++--
1 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/builtin-add.c b/builtin-add.c
index dd895df..7c6a296 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -138,9 +138,10 @@ static void refresh(int verbose, const char **pathspec)
int interactive_add(int argc, const char **argv)
{
int status;
- const char **args = xmalloc(sizeof(const char *) * (argc + 1));
+ const char **args = xcalloc(sizeof(const char *), (argc + 2));
+
args[0] = "add--interactive";
- memcpy((void *)args + sizeof(const char *), argv, sizeof(const char *) * argc);
+ memcpy(&(args[1]), argv, sizeof(const char *) * argc);
args[argc + 1] = NULL;
status = run_command_v_opt(args, RUN_GIT_CMD);
--
1.5.3.6.2014.g7500f
^ permalink raw reply related
* [PATCH] add -i: Fix running from a subdirectory
From: Junio C Hamano @ 2007-11-25 18:10 UTC (permalink / raw)
To: Wincent Colaiuta; +Cc: git, peff
In-Reply-To: <1195996542-86074-1-git-send-email-win@wincent.com>
This fixes the pathspec interactive_add() passes to the underlying
git-add--interactive helper. When the command was run from a
subdirectory, cmd_add() already has gone up to the toplevel of the work
tree, and the helper will be spawned from there. The pathspec given on
the command line from the user needs to be adjusted for this.
This adds "validate_pathspec()" function in the callchain, but it does
not validate yet. The function can be changed to barf if there are
unmatching pathspec given by the user, but that is not strictly
necessary.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
* The reason it is not "strictly necessary" is because if you give
unmatching pathspecs, you simply would not see their effects (good or
bad) in any of the subcommands in git-add--interactive.
builtin-add.c | 24 ++++++++++++++++++++----
builtin-commit.c | 2 +-
commit.h | 2 +-
3 files changed, 22 insertions(+), 6 deletions(-)
diff --git a/builtin-add.c b/builtin-add.c
index 7c6a296..865c475 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -135,13 +135,29 @@ static void refresh(int verbose, const char **pathspec)
free(seen);
}
-int interactive_add(int argc, const char **argv)
+static const char **validate_pathspec(int argc, const char **argv, const char *prefix)
+{
+ const char **pathspec = get_pathspec(prefix, argv);
+
+ return pathspec;
+}
+
+int interactive_add(int argc, const char **argv, const char *prefix)
{
int status;
- const char **args = xcalloc(sizeof(const char *), (argc + 2));
+ const char **args;
+ const char **pathspec = NULL;
+
+ if (argc) {
+ pathspec = validate_pathspec(argc, argv, prefix);
+ if (!pathspec)
+ return -1;
+ }
+ args = xcalloc(sizeof(const char *), (argc + 2));
args[0] = "add--interactive";
- memcpy(&(args[1]), argv, sizeof(const char *) * argc);
+ if (argc)
+ memcpy(&(args[1]), pathspec, sizeof(const char *) * argc);
args[argc + 1] = NULL;
status = run_command_v_opt(args, RUN_GIT_CMD);
@@ -177,7 +193,7 @@ int cmd_add(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, builtin_add_options,
builtin_add_usage, 0);
if (add_interactive)
- exit(interactive_add(argc, argv));
+ exit(interactive_add(argc, argv, prefix));
git_config(git_default_config);
diff --git a/builtin-commit.c b/builtin-commit.c
index 5d27102..45e51b1 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -165,7 +165,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
const char **pathspec = NULL;
if (interactive) {
- interactive_add(argc, argv);
+ interactive_add(argc, argv, prefix);
commit_style = COMMIT_AS_IS;
return get_index_file();
}
diff --git a/commit.h b/commit.h
index 9f0765b..10e2b5d 100644
--- a/commit.h
+++ b/commit.h
@@ -113,7 +113,7 @@ extern struct commit_list *get_shallow_commits(struct object_array *heads,
int in_merge_bases(struct commit *, struct commit **, int);
-extern int interactive_add(int argc, const char **argv);
+extern int interactive_add(int argc, const char **argv, const char *prefix);
extern int rerere(void);
static inline int single_parent(struct commit *commit)
--
1.5.3.6.2014.g7500f
^ permalink raw reply related
* Re: [PATCH 1/3] Rename patch_update_file function to patch_update_pathspec
From: Junio C Hamano @ 2007-11-25 18:11 UTC (permalink / raw)
To: Wincent Colaiuta; +Cc: git, peff
In-Reply-To: <1195996542-86074-2-git-send-email-win@wincent.com>
Wincent Colaiuta <win@wincent.com> writes:
> The patch_update_file function really works on pathspecs, not files, so
> rename it to reflect its actual purpose.
I do not think this is a correct change. patch_update_file() should
work on files not pathspecs.
A list of exact pathnames are valid pathspecs, and because the matching
function takes exact match first, if you feed a command
(e.g. diff-files) paths expanded from the pathspec given by the user,
you should be able to get desired results, no?
patch_update_cmd() grabs list of modified _files_, not pathspecs. Then
the user chooses the ones to select hunks from via list_and_choose(),
and you may want to pretend that everything was chosen if you are
operating with pathspec and with --patch option. Iterating over them is
iterating over files, not pathspecs, at that point.
Have you tried the one that is in 'next'? I think it does the right
thing except "the bogus pathspec validation at the beginning" part as
far as the pathspec handling is concerned.
I sent out two fixes on top of the series.
^ permalink raw reply
* Re: [PATCH] builtin-add: fix command line building to call interactive
From: Wincent Colaiuta @ 2007-11-25 18:27 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, peff
In-Reply-To: <7vd4tynqpw.fsf@gitster.siamese.dyndns.org>
El 25/11/2007, a las 19:07, Junio C Hamano escribió:
> The earlier 7c0ab4458994aa895855abc4a504cf693ecc0cf1 (Teach builtin-
> add
> to pass multiple paths to git-add--interactive) did not allocate
> enough,
Yes, it was off by one; sorry about that. You may have noticed that I
fixed that up in the patches I sent out yesterday and today. May need
to redo them now to apply on top of this.
Cheers,
Wincent
^ permalink raw reply
* [PATCH] git-checkout: describe detached head correctly
From: Junio C Hamano @ 2007-11-25 18:34 UTC (permalink / raw)
To: git
When you have a file called HEAD in the work tree, the code to report
where the HEAD is at when "git checkout $commit^0" is done triggered
unnecessary ambiguity checking.
Explicitly mark the command line with "--" and make it clear that we are
talking about a revision.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
git-checkout.sh | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/git-checkout.sh b/git-checkout.sh
index 17f4392..93dfb46 100755
--- a/git-checkout.sh
+++ b/git-checkout.sh
@@ -169,7 +169,7 @@ detach_warn=
describe_detached_head () {
test -n "$quiet" || {
printf >&2 "$1 "
- GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2"
+ GIT_PAGER= git log >&2 -1 --pretty=oneline --abbrev-commit "$2" --
}
}
^ permalink raw reply related
* Re: [PATCH] builtin-add: fix command line building to call interactive
From: Junio C Hamano @ 2007-11-25 18:36 UTC (permalink / raw)
To: Wincent Colaiuta; +Cc: git, peff
In-Reply-To: <F09249EB-475A-4352-A3A1-A8B15D2A94FF@wincent.com>
Wincent Colaiuta <win@wincent.com> writes:
> El 25/11/2007, a las 19:07, Junio C Hamano escribi> The earlier 7c0ab4458994aa895855abc4a504cf693ecc0cf1 (Teach builtin-
>> add
>> to pass multiple paths to git-add--interactive) did not allocate
>> enough,
>
> Yes, it was off by one; sorry about that. You may have noticed that I
> fixed that up in the patches I sent out yesterday and today. May need
> to redo them now to apply on top of this.
I'd suggest you to slow down, apply the two patches on top of 'next' and
take a look at the result.
I _think_ the only remaining thing is --patch, and none of the pathspec
thing is needed.
^ permalink raw reply
* Re: [PATCH] builtin-add: fix command line building to call interactive
From: Wincent Colaiuta @ 2007-11-25 19:02 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, peff
In-Reply-To: <7vlk8mmatu.fsf@gitster.siamese.dyndns.org>
El 25/11/2007, a las 19:36, Junio C Hamano escribió:
> I _think_ the only remaining thing is --patch, and none of the
> pathspec
> thing is needed.
You're probably right; the pathspec validation is probably not
necessary (and you may recall that my original patch didn't include
it; I only tried adding after you said it might be appropriate to have
the "--error-unmatch" behaviour). This is probably more convenient for
the user, as it allows them to pass "sloppy" parameters like the
following:
git-add -i *.h
(Note that's "*.h" and not "\*.h"). In the Git repository, without
validation, this just works. With strict validation, it would complain:
error: pathspec 'common-cmds.h' did not match any file(s) known to git.
So just forgetting about the validation is probably the right thing to
do.
As for adding the --patch option, I'll stand back and see if someone
more skilled than I wants to do it; should only be a few lines and
will save traffic to the list because they'll probably get it right
first time.
Cheers,
Wincent
^ permalink raw reply
* Re: [PATCH] builtin-add: fix command line building to call interactive
From: Junio C Hamano @ 2007-11-25 19:48 UTC (permalink / raw)
To: Wincent Colaiuta; +Cc: git, peff
In-Reply-To: <22CBC161-57C2-4A7E-9415-CE8117758A44@wincent.com>
Wincent Colaiuta <win@wincent.com> writes:
>> I _think_ the only remaining thing is --patch, and none of the
>> pathspec
>> thing is needed.
>
> You're probably right; the pathspec validation is probably not
> necessary (and you may recall that my original patch didn't include
> it; I only tried adding after you said it might be appropriate to have
> the "--error-unmatch" behaviour)...
Oh, I did not mean the validation bits I left out in the second "fixup"
patch I sent earlier by that comment. What I meant was the changes you
had in git-add--interactive::patch_update_cmd() to make it take non
files but pathspecs.
> This is probably more convenient for
> the user, as it allows them to pass "sloppy" parameters like the
> following:
>
> git-add -i *.h
>
> (Note that's "*.h" and not "\*.h"). In the Git repository, without
> validation, this just works. With strict validation, it would complain...
I'd mostly agree, but we need to realize that this is a two edged sword.
Pathspecs can be leading-directories or fileglobs. For fileglobs, you
are right. The user can let the shell do the globbing. Not validating,
however, also means that
git add -p Documentatoin
would report "there is nothing to patch" without being helpful, pointing
out that the name of the directory is misspelled.
> As for adding the --patch option, I'll stand back and see if someone
> more skilled than I wants to do it; should only be a few lines and
> will save traffic to the list because they'll probably get it right
> first time.
And that skilled person turns out to be you ;-).
At the end of this message is your 3/3 from yesterday, minimally
adjusted to the current tip of 'next'.
I tweaked it to allow:
git add -p
without any pathspecs, which I think is a valid usage.
-- >8 --
[PATCH] Add "--patch" option to git-add--interactive
When the "--patch" option is supplied, the patch_update_cmd() function is
called bypassing the main_loop() and exits.
Seeing as builtin-add is the only caller of git-add--interactive we can
impose a strict requirement on the format of the arguments to avoid
possible ambiguity: an "--" argument must be used whenever any pathspecs
are passed, both with the "--patch" option and without it.
Signed-off-by: Wincent Colaiuta <win@wincent.com>
---
Documentation/git-add.txt | 9 +++++++-
builtin-add.c | 24 ++++++++++++++-------
git-add--interactive.perl | 51 ++++++++++++++++++++++++++++++++++++--------
3 files changed, 65 insertions(+), 19 deletions(-)
diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt
index 63829d9..ce22de8 100644
--- a/Documentation/git-add.txt
+++ b/Documentation/git-add.txt
@@ -61,7 +61,14 @@ OPTIONS
-i, \--interactive::
Add modified contents in the working tree interactively to
- the index.
+ the index. Optional path arguments may be supplied to limit
+ operation to a subset of the working tree. See ``Interactive
+ mode'' for details.
+
+-p, \--patch:
+ Similar to Interactive mode but the initial command loop is
+ bypassed and the 'patch' subcommand is invoked using each of
+ the specified filepatterns before exiting.
-u::
Update only files that git already knows about. This is similar
diff --git a/builtin-add.c b/builtin-add.c
index 865c475..5c29cc2 100644
--- a/builtin-add.c
+++ b/builtin-add.c
@@ -19,7 +19,7 @@ static const char * const builtin_add_usage[] = {
"git-add [options] [--] <filepattern>...",
NULL
};
-
+static int patch_interactive = 0, add_interactive = 0;
static int take_worktree_changes;
static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
@@ -144,7 +144,7 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p
int interactive_add(int argc, const char **argv, const char *prefix)
{
- int status;
+ int status, ac;
const char **args;
const char **pathspec = NULL;
@@ -154,11 +154,17 @@ int interactive_add(int argc, const char **argv, const char *prefix)
return -1;
}
- args = xcalloc(sizeof(const char *), (argc + 2));
- args[0] = "add--interactive";
- if (argc)
- memcpy(&(args[1]), pathspec, sizeof(const char *) * argc);
- args[argc + 1] = NULL;
+ args = xcalloc(sizeof(const char *), (argc + 4));
+ ac = 0;
+ args[ac++] = "add--interactive";
+ if (patch_interactive)
+ args[ac++] = "--patch";
+ args[ac++] = "--";
+ if (argc) {
+ memcpy(&(args[ac]), pathspec, sizeof(const char *) * argc);
+ ac += argc;
+ }
+ args[ac] = NULL;
status = run_command_v_opt(args, RUN_GIT_CMD);
free(args);
@@ -171,13 +177,13 @@ static const char ignore_error[] =
"The following paths are ignored by one of your .gitignore files:\n";
static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
-static int add_interactive = 0;
static struct option builtin_add_options[] = {
OPT__DRY_RUN(&show_only),
OPT__VERBOSE(&verbose),
OPT_GROUP(""),
OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
+ OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"),
OPT_BOOLEAN('f', NULL, &ignored_too, "allow adding otherwise ignored files"),
OPT_BOOLEAN('u', NULL, &take_worktree_changes, "update tracked files"),
OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
@@ -192,6 +198,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, builtin_add_options,
builtin_add_usage, 0);
+ if (patch_interactive)
+ add_interactive = 1;
if (add_interactive)
exit(interactive_add(argc, argv, prefix));
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
index e347216..df5df3e 100755
--- a/git-add--interactive.perl
+++ b/git-add--interactive.perl
@@ -2,6 +2,9 @@
use strict;
+# command line options
+my $patch_mode;
+
sub run_cmd_pipe {
if ($^O eq 'MSWin32') {
my @invalid = grep {m/[":*]/} @_;
@@ -552,8 +555,8 @@ sub help_patch_cmd {
print <<\EOF ;
y - stage this hunk
n - do not stage this hunk
-a - stage this and all the remaining hunks
-d - do not stage this hunk nor any of the remaining hunks
+a - stage this and all the remaining hunks in the file
+d - do not stage this hunk nor any of the remaining hunks in the file
j - leave this hunk undecided, see next undecided hunk
J - leave this hunk undecided, see next hunk
k - leave this hunk undecided, see previous undecided hunk
@@ -563,13 +566,21 @@ EOF
}
sub patch_update_cmd {
- my @mods = list_modified('file-only');
- @mods = grep { !($_->{BINARY}) } @mods;
- return if (!@mods);
+ my @mods = grep { !($_->{BINARY}) } list_modified('file-only');
+ my @them;
- my (@them) = list_and_choose({ PROMPT => 'Patch update',
- HEADER => $status_head, },
- @mods);
+ if (!@mods) {
+ print STDERR "No changes.\n";
+ return 0;
+ }
+ if ($patch_mode) {
+ @them = @mods;
+ }
+ else {
+ @them = list_and_choose({ PROMPT => 'Patch update',
+ HEADER => $status_head, },
+ @mods);
+ }
for (@them) {
patch_update_file($_->{VALUE});
}
@@ -783,6 +794,20 @@ add untracked - add contents of untracked files to the staged set of changes
EOF
}
+sub process_args {
+ return unless @ARGV;
+ my $arg = shift @ARGV;
+ if ($arg eq "--patch") {
+ $patch_mode = 1;
+ $arg = shift @ARGV or die "missing --";
+ die "invalid argument $arg, expecting --"
+ unless $arg eq "--";
+ }
+ elsif ($arg ne "--") {
+ die "invalid argument $arg, expecting --";
+ }
+}
+
sub main_loop {
my @cmd = ([ 'status', \&status_cmd, ],
[ 'update', \&update_cmd, ],
@@ -811,6 +836,12 @@ sub main_loop {
}
}
+process_args();
refresh();
-status_cmd();
-main_loop();
+if ($patch_mode) {
+ patch_update_cmd();
+}
+else {
+ status_cmd();
+ main_loop();
+}
--
1.5.3.6.2018.g84394
^ permalink raw reply related
* What's cooking in git.git (topics)
From: Junio C Hamano @ 2007-11-25 20:27 UTC (permalink / raw)
To: git
In-Reply-To: <7vve7tuz3a.fsf@gitster.siamese.dyndns.org>
Here are the topics that have been cooking. Commits prefixed
with '-' are only in 'pu' while commits prefixed with '+' are
in 'next'. The topics list the commits in reverse chronological
order.
----------------------------------------------------------------
[Graduated to 'master']
* cc/bisect (Tue Nov 20 06:39:53 2007 +0100) 7 commits
* mh/rebase-skip-hard (Thu Nov 8 08:03:06 2007 +0100) 1 commit
* js/mingw-fallouts (Sat Nov 17 23:09:28 2007 +0100) 13 commits
* sb/clean (Wed Nov 14 23:00:54 2007 -0600) 3 commits
* jk/send-pack (Tue Nov 20 06:18:01 2007 -0500) 29 commits
----------------------------------------------------------------
[Graduated to 'maint']
* jc/maint-format-patch-encoding (Fri Nov 2 17:55:31 2007 -0700) 2 commits
* lt/maint-rev-list-gitlink (Sun Nov 11 23:35:23 2007 +0000) 1 commit
----------------------------------------------------------------
[New Topics]
* jc/api-doc (Sat Nov 24 23:48:04 2007 -0800) 1 commit
- Start preparing the API documents.
This currently consists of mostly stubs, although I wrote about a few
topics as examples.
* jc/diff-pathspec (Sun Nov 25 10:03:48 2007 -0800) 1 commit
- Making ce_path_match() more useful by accepting globs
----------------------------------------------------------------
[Will cook further in 'next' and then merge to 'master' soon]
* jc/move-gitk (Sat Nov 17 10:51:16 2007 -0800) 1 commit
+ Move gitk to its own subdirectory
I have a phoney Makefile under the subdirectory for gitk, but
hopefully with the next pull from Paulus I can replace it with
the real thing, along with the i18n stuff.
* tt/help (Sun Nov 11 19:57:57 2007 -0500) 2 commits
+ Remove hint to use "git help -a"
+ Make the list of common commands more exclusive
Some people on the list may find the exact list of commands
somewhat debatable. We can fine-tune that in-tree ('pu' does
not count as "in-tree").
* kh/commit (Thu Nov 22 16:21:49 2007 -0800) 23 commits
+ Add a few more tests for git-commit
+ builtin-commit: Include the diff in the commit message when
verbose.
+ builtin-commit: fix partial-commit support
+ Fix add_files_to_cache() to take pathspec, not user specified list
of files
+ Export three helper functions from ls-files
+ builtin-commit: run commit-msg hook with correct message file
+ builtin-commit: do not color status output shown in the message
template
+ file_exists(): dangling symlinks do exist
+ Replace "runstatus" with "status" in the tests
+ t7501-commit: Add test for git commit <file> with dirty index.
+ builtin-commit: Clean up an unused variable and a debug fprintf().
+ Call refresh_cache() when updating the user index for --only
commits.
+ builtin-commit: Add newline when showing which commit was created
+ builtin-commit: resurrect behavior for multiple -m options
+ builtin-commit --s: add a newline if the last line was not a S-o-b
+ builtin-commit: fix --signoff
+ git status: show relative paths when run in a subdirectory
+ builtin-commit: Refresh cache after adding files.
+ builtin-commit: fix reflog message generation
+ launch_editor(): read the file, even when EDITOR=:
+ Port git commit to C.
+ Export launch_editor() and make it accept ':' as a no-op editor.
+ Add testcase for amending and fixing author in git commit.
I've been running with this, and so are people following 'next', for a
few days. The series seems to be in a good shape.
* cr/tag-options (Thu Nov 22 23:16:51 2007 -0800) 2 commits
+ builtin-tag: accept and process multiple -m just like git-commit
+ Make builtin-tag.c use parse_options.
The handling of multiple -m options are made consistent with
what git-commit does; i.e. they are concatenated as separate
paragraphs.
* jc/branch-contains (Sun Nov 18 22:22:00 2007 -0800) 3 commits
+ git-branch --contains: doc and test
+ git-branch --contains=commit
+ parse-options: Allow to hide options from the default usage.
Contains Pierre's "hidable options with --help-all" patch.
----------------------------------------------------------------
[Actively cooking]
* jc/spht (Sat Nov 24 11:57:41 2007 -0800) 6 commits
+ core.whitespace: documentation updates.
+ builtin-apply: teach whitespace_rules
+ builtin-apply: rename "whitespace" variables and fix styles
+ core.whitespace: add test for diff whitespace error highlighting
+ git-diff: complain about >=8 consecutive spaces in initial indent
+ War on whitespace: first, a bit of retreat.
Now apply also knows about the customizable definition of what
whitespace breakages are, and I was reasonably happy. But Bruce kicked
it back from "scheduled to merge" to "still cooking" status, reminding
that we would want to have this not a tree-wide configuration but
per-path attribute. And I agree with him.
* wc/add-i (Sun Nov 25 14:15:42 2007 +0100) 30 commits
- Add "--patch" option to git-add--interactive
+ add -i: Fix running from a subdirectory
+ builtin-add: fix command line building to call interactive
+ Merge branch 'kh/commit' into wc/add-i
+ Add a few more tests for git-commit
+ git-add -i: allow multiple selection in patch subcommand
+ builtin-commit: Include the diff in the commit message when
verbose.
+ builtin-commit: fix partial-commit support
+ Fix add_files_to_cache() to take pathspec, not user specified list
of files
+ Export three helper functions from ls-files
+ builtin-commit: run commit-msg hook with correct message file
+ builtin-commit: do not color status output shown in the message
template
+ file_exists(): dangling symlinks do exist
+ Replace "runstatus" with "status" in the tests
+ t7501-commit: Add test for git commit <file> with dirty index.
+ builtin-commit: Clean up an unused variable and a debug fprintf().
+ Call refresh_cache() when updating the user index for --only
commits.
+ builtin-commit: Add newline when showing which commit was created
+ builtin-commit: resurrect behavior for multiple -m options
+ builtin-commit --s: add a newline if the last line was not a S-o-b
+ builtin-commit: fix --signoff
+ git status: show relative paths when run in a subdirectory
+ builtin-commit: Refresh cache after adding files.
+ builtin-commit: fix reflog message generation
+ launch_editor(): read the file, even when EDITOR=:
+ Port git commit to C.
+ Export launch_editor() and make it accept ':' as a no-op editor.
+ Add testcase for amending and fixing author in git commit.
+ Add path-limiting to git-add--interactive
+ Teach builtin-add to pass multiple paths to git-add--interactive
This looks larger than it really is, as I merged in the builtin commit
series near the tip (they interact with each other somewhat, and it is
very likely that builtin commit series will graduate to 'master' before
this series).
I also adjusted the "git add -p" patch from Wincent and have it at the
tip. It is parked in 'pu' for now.
* sp/refspec-match (Sun Nov 11 15:01:48 2007 +0100) 4 commits
+ refactor fetch's ref matching to use refname_match()
+ push: use same rules as git-rev-parse to resolve refspecs
+ add refname_match()
+ push: support pushing HEAD to real branch name
I think the "git push HEAD" is a good change, and also using the same
short refname resolving as rev-parse does for matching the destination
of push. I am having second thoughts on the last one. The changed
semantics is somewhat less safe:
* We did not allow fetching outside refs/ (except HEAD), but now we
allow any random string.
* We used to restrict fetching names that do not begin with refs/ to
heads, tags and remotes, but now the code grabs anything underneath
refs/.
which could invite mistakes by letting typos slip through.
Having said that, I probably "fetch" much less often than other people
do and these are non issues in the real-world usecases. It could be
that I am worried too much needlessly. If anybody who is following
'next' has been bitten by the change, please speak up.
----------------------------------------------------------------
[Stalled]
* jc/cherry-pick (Tue Nov 13 12:38:51 2007 -0800) 1 commit
- revert/cherry-pick: start refactoring call to merge_recursive
* jc/nu (Sun Oct 14 22:07:34 2007 -0700) 3 commits
- merge-nu: a new merge backend without using unpack_trees()
- read_tree: take an explicit index structure
- gcc 4.2.1 -Werror -Wall -ansi -pedantic -std=c99: minimum fix
The second one could probably be used to replace the use of
path-list in the tip commit on the kh/commit series.
* dz/color-addi (Sat Nov 10 18:03:44 2007 -0600) 3 commits
- Added diff hunk coloring to git-add--interactive
- Let git-add--interactive read colors from .gitconfig
- Added basic color support to git add --interactive
There were many good suggestions by Jeff to the updated series;
hopefully we can replace these three with it.
* nd/maint-work-tree-fix (Sat Nov 3 20:18:06 2007 +0700) 1 commit
+ Add missing inside_work_tree setting in setup_git_directory_gently
There was an additional patch, which still had issues Dscho
pointed out. Waiting for refinements.
* js/reflog-delete (Wed Oct 17 02:50:45 2007 +0100) 1 commit
+ Teach "git reflog" a subcommand to delete single entries
* jc/pathspec (Thu Sep 13 13:38:19 2007 -0700) 3 commits
- pathspec_can_match(): move it from builtin-ls-tree.c to tree.c
- ls-tree.c: refactor show_recursive() and rename it.
- tree-diff.c: split out a function to match a single pattern.
* ss/dirty-rebase (Thu Nov 1 22:30:24 2007 +0100) 3 commits
. Make git-svn rebase --dirty pass along --dirty to git-rebase.
. Implement --dirty for git-rebase--interactive.
. Introduce --dirty option to git-rebase, allowing you to start from
a dirty state.
This seems to be optimized for the --dirty case too much. I'd
prefer an implementation that make rebases without --dirty to
pay no penalty (if that is possible, otherwise "as little as
possible").
* jk/rename (Tue Oct 30 00:24:42 2007 -0400) 3 commits
. handle renames using similarity engine
. introduce generic similarity library
. change hash table calling conventions
This was an attempt to use different strategy to speed up
similarity computation, but does not work quite well as is.
^ permalink raw reply
* [StGit PATCH 0/2] Two small doc fixes
From: Karl Hasselström @ 2007-11-25 20:35 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git
---
Also available from
git://repo.or.cz/stgit/kha.git safe
Karl Hasselström (2):
Add missing switch to "stg uncommit" usage line
Push and pop don't have --to flags anymore
stgit/commands/goto.py | 5 ++---
stgit/commands/uncommit.py | 2 +-
2 files changed, 3 insertions(+), 4 deletions(-)
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle
^ permalink raw reply
* [RFC/PATCH] Making ce_path_match() more useful by accepting globs
From: Junio C Hamano @ 2007-11-25 20:35 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
Currently, these do not work:
git diff-files 't/*.sh'
git diff-index HEAD 'xdiff/*.c'
git update-index -g 'Documentation/howto/*.txt'
This is because ce_path_match(), the underlying function that is used to
see if a cache entry matches the set of pathspecs, only understands
leading directory match.
This teaches ce_path_match() to use the match_pathspec() used in
git-ls-files, which knows about glob patterns.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
[SORRY FOR A RESEND -- I screwed up the To: field of the previous message]
* Having two different behaviours of pathspec matching has been
bothering me for quite some time. The changes here look trivially
correct and the result passes all the tests, but this is quite close
to the core part of the system, and would benefit greatly from extra
set of eyes.
This patch does not touch the tree walker, and does not affect
diff-tree nor ancestry pruning done in the revision traversal. That
however is even closer to the core and is performance critical. It
needs to be done carefully not to descend into trees that would never
match needlessly. IOW, not today.
dir.c | 5 +++--
read-cache.c | 17 ++---------------
2 files changed, 5 insertions(+), 17 deletions(-)
diff --git a/dir.c b/dir.c
index 225fdfb..be640c9 100644
--- a/dir.c
+++ b/dir.c
@@ -98,20 +98,21 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, int pre
{
int retval;
const char *match;
+ int want_seen = !!seen;
name += prefix;
namelen -= prefix;
for (retval = 0; (match = *pathspec++) != NULL; seen++) {
int how;
- if (retval && *seen == MATCHED_EXACTLY)
+ if (retval && seen && want_seen && *seen == MATCHED_EXACTLY)
continue;
match += prefix;
how = match_one(match, name, namelen);
if (how) {
if (retval < how)
retval = how;
- if (*seen < how)
+ if (want_seen && *seen < how)
*seen = how;
}
}
diff --git a/read-cache.c b/read-cache.c
index 7db5588..767464e 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -472,7 +472,7 @@ int ce_same_name(struct cache_entry *a, struct cache_entry *b)
int ce_path_match(const struct cache_entry *ce, const char **pathspec)
{
- const char *match, *name;
+ const char *name;
int len;
if (!pathspec)
@@ -480,20 +480,7 @@ int ce_path_match(const struct cache_entry *ce, const char **pathspec)
len = ce_namelen(ce);
name = ce->name;
- while ((match = *pathspec++) != NULL) {
- int matchlen = strlen(match);
- if (matchlen > len)
- continue;
- if (memcmp(name, match, matchlen))
- continue;
- if (matchlen && name[matchlen-1] == '/')
- return 1;
- if (name[matchlen] == '/' || !name[matchlen])
- return 1;
- if (!matchlen)
- return 1;
- }
- return 0;
+ return !!match_pathspec(pathspec, name, len, 0, NULL);
}
/*
^ permalink raw reply related
* [StGit PATCH 1/2] Push and pop don't have --to flags anymore
From: Karl Hasselström @ 2007-11-25 20:35 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git
In-Reply-To: <20071125203346.7640.80801.stgit@yoghurt>
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
stgit/commands/goto.py | 5 ++---
1 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/stgit/commands/goto.py b/stgit/commands/goto.py
index 7f9d45d..84b840b 100644
--- a/stgit/commands/goto.py
+++ b/stgit/commands/goto.py
@@ -27,9 +27,8 @@ help = 'push or pop patches to the given one'
usage = """%prog [options] <name>
Push/pop patches to/from the stack until the one given on the command
-line becomes current. This is a shortcut for the 'push --to' or 'pop
---to' commands. There is no '--undo' option for 'goto'. Use the 'push'
-command for this."""
+line becomes current. There is no '--undo' option for 'goto'. Use the
+'push --undo' command for this."""
directory = DirectoryGotoToplevel()
options = [make_option('-k', '--keep',
^ permalink raw reply related
* [StGit PATCH 2/2] Add missing switch to "stg uncommit" usage line
From: Karl Hasselström @ 2007-11-25 20:35 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git
In-Reply-To: <20071125203346.7640.80801.stgit@yoghurt>
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
stgit/commands/uncommit.py | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/stgit/commands/uncommit.py b/stgit/commands/uncommit.py
index 9a46c94..ba3448f 100644
--- a/stgit/commands/uncommit.py
+++ b/stgit/commands/uncommit.py
@@ -26,7 +26,7 @@ from stgit.out import *
from stgit import stack, git
help = 'turn regular GIT commits into StGIT patches'
-usage = """%prog [<patchnames>] | -n NUM [<prefix>]] | -t <committish>
+usage = """%prog [<patchnames>] | -n NUM [<prefix>]] | -t <committish> [-x]
Take one or more git commits at the base of the current stack and turn
them into StGIT patches. The new patches are created as applied patches
^ permalink raw reply related
* Re: What's cooking in git.git (topics)
From: Jakub Narebski @ 2007-11-25 20:36 UTC (permalink / raw)
To: git
In-Reply-To: <7v4pfakr4j.fsf@gitster.siamese.dyndns.org>
Junio C Hamano wrote:
> [Actively cooking]
>
> * jc/spht (Sat Nov 24 11:57:41 2007 -0800) 6 commits
> + core.whitespace: documentation updates.
> + builtin-apply: teach whitespace_rules
> + builtin-apply: rename "whitespace" variables and fix styles
> + core.whitespace: add test for diff whitespace error highlighting
> + git-diff: complain about >=8 consecutive spaces in initial indent
> + War on whitespace: first, a bit of retreat.
>
> Now apply also knows about the customizable definition of what
> whitespace breakages are, and I was reasonably happy. But Bruce kicked
> it back from "scheduled to merge" to "still cooking" status, reminding
> that we would want to have this not a tree-wide configuration but
> per-path attribute. And I agree with him.
Currently apply.whitespace is per repository - would this be changed
as well, i.e. would it be moved to gitattributes together with custom
diff drivers (or at least custom funcnames), custom merge drivers,
making it per-project (if put under version control) and per-path?
By the way, i18n.commitEncoding is per repository, and used to affect
repository; not so with the "encoding" header in commit object.
--
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
^ permalink raw reply
* [BUGLET] Makefile uses $(SCRIPT_PERL) but Documentation/Makefile uses "perl"
From: Randal L. Schwartz @ 2007-11-25 20:39 UTC (permalink / raw)
To: git
On my system, that means the two Makefiles actually use two different
Perl installations. It'd be best if someone who is smarter at Makefiles
than I am would make them work the same. Thanks.
--
Randal L. Schwartz - Stonehenge Consulting Services, Inc. - +1 503 777 0095
<merlyn@stonehenge.com> <URL:http://www.stonehenge.com/merlyn/>
Perl/Unix/security consulting, Technical writing, Comedy, etc. etc.
See PerlTraining.Stonehenge.com for onsite and open-enrollment Perl training!
^ permalink raw reply
* What's in git.git (stable)
From: Junio C Hamano @ 2007-11-25 20:45 UTC (permalink / raw)
To: git
In-Reply-To: <7vy7cwsi3p.fsf@gitster.siamese.dyndns.org>
There are quite a few backported fixes on 'maint' and it might make
sense to cut 1.5.3.7 soon.
On 'master' front, many topics that have been cooking in 'next' have
graduated. Notably:
- git-bisect learns "skip";
- git-rebase --skip does not need "reset --hard" beforehand;
- git-clean is now in C;
- git-push is much more careful reporting errors and updateing tracking
refs.
- git-push learns --mirror option.
Also contains the 0.9.0 series of git-gui with i18n.
----------------------------------------------------------------
* The 'maint' branch has these fixes since the last announcement.
Björn Steinbrink (3):
git-commit.sh: Fix usage checks regarding paths given when they do
not make sense
t7005-editor.sh: Don't invoke real vi when it is in GIT_EXEC_PATH
git-commit: Add tests for invalid usage of -a/--interactive with paths
Brian Downing (2):
config: correct core.loosecompression documentation
config: clarify compression defaults
J. Bruce Fields (3):
git-remote.txt: fix example url
user-manual: mention "..." in "Generating diffs", etc.
Documentation: Fix references to deprecated commands
Jeff King (1):
send-email: add transfer encoding header with content-type
Johannes Schindelin (1):
bundle create: keep symbolic refs' names instead of resolving them
Junio C Hamano (10):
format-patch -s: add MIME encoding header if signer's name requires so
test format-patch -s: make sure MIME content type is shown as needed
ce_match_stat, run_diff_files: use symbolic constants for readability
git-add: make the entry stat-clean after re-adding the same contents
t2200: test more cases of "add -u"
grep -An -Bm: fix invocation of external grep command
GIT 1.5.3.6
Make test scripts executable.
Fix sample pre-commit hook
git-checkout: describe detached head correctly
Linus Torvalds (1):
Fix rev-list when showing objects involving submodules
Matthieu Moy (1):
Doc fix for git-reflog: mention @{...} syntax, and <ref> in synopsys.
Rémi Vanicat (1):
Make GIT_INDEX_FILE apply to git-commit
Steffen Prohaska (1):
user-manual: Add section "Why bisecting merge commits can be harder ..."
----------------------------------------------------------------
* The 'master' branch has these since the last announcement
in addition to the above.
Alex Riesen (4):
More updates and corrections to the russian translation of git-gui
Updated russian translation of git-gui
Add a test checking if send-pack updated local tracking branches
correctly
Update the tracking references only if they were succesfully
updated on remote
Andy Whitcroft (5):
Teach send-pack a mirror mode
git-push: plumb in --mirror mode
Add tests for git push'es mirror mode
git-push: add documentation for the newly added --mirror mode
git-quiltimport.sh fix --patches handling
Anton Gyllenberg (1):
gitview: import only one of gtksourceview and gtksourceview2
Ask Bjørn Hansen (1):
send-email: Don't add To: recipients to the Cc: header
Christian Couder (4):
Bisect reset: remove bisect refs that may have been packed.
Bisect visualize: use "for-each-ref" to list all good refs.
Bisect: use "$GIT_DIR/BISECT_NAMES" to check if we are bisecting.
Bisect reset: do nothing when not bisecting.
Christian Stimming (12):
Mark strings for translation.
Makefile rules for translation catalog generation and installation.
Add glossary that can be converted into a po file for each
language.
Add glossary translation template into git.
German translation for git-gui
German glossary for translation
git-gui: Add more words to translation glossary
git-gui: Update German glossary according to mailing list
discussion
git-gui: Incorporate glossary changes into existing German
translation
git-gui: Update German translation, including latest glossary
changes
git-gui: Add more terms to glossary.
git-gui: Update German translation
Daniel Barkalow (5):
Miscellaneous const changes and utilities
Build-in peek-remote, using transport infrastructure.
Use built-in send-pack.
Build-in send-pack, with an API for other programs to call.
Build in ls-remote
David D Kilzer (3):
git-svn log: fix ascending revision ranges
git-svn log: include commit log for the smallest revision in a
range
git-svn log: handle unreachable revisions like "svn log"
David D. Kilzer (4):
git-send-email: show all headers when sending mail
git-svn: extract reusable code into utility functions
git-svn info: implement info command
git-svn: info --url [path]
David Reiss (1):
git-svn: Fix a typo and add a comma in an error message in git-svn
David Symonds (2):
git-checkout: Support relative paths containing "..".
git-checkout: Test for relative path use.
Eric Wong (3):
git-svn: add tests for command-line usage of init and clone
commands
t9106: fix a race condition that caused svn to miss modifications
git-svn: allow `info' command to work offline
Guido Ostkamp (1):
Use compat mkdtemp() on Solaris boxes
Harri Ilari Tapio Liusvaara (1):
git-gui: Disambiguate "commit"
Irina Riesen (1):
git-gui: initial version of russian translation
Jakub Narebski (3):
gitweb: Style all tables using CSS
gitweb: Put project README in div.readme, fix its padding
autoconf: Add tests for memmem, strtoumax and mkdtemp functions
Jeff King (11):
more terse push output
receive-pack: don't mention successful updates
send-pack: require --verbose to show update of tracking refs
send-pack: track errors for each ref
send-pack: check ref->status before updating tracking refs
send-pack: assign remote errors to each ref
make "find_ref_by_name" a public function
send-pack: tighten remote error reporting
send-pack: fix "everything up-to-date" message
avoid "defined but not used" warning for fetch_objs_via_walker
send-pack: cluster ref status reporting
Johannes Schindelin (9):
Add po/git-gui.pot
Ignore po/*.msg
git-gui: Deiconify startup wizard so it raises to the top
git-gui: add a simple msgfmt replacement
po2msg: ignore entries marked with "fuzzy"
po2msg: ignore untranslated messages
po2msg: actually output statistics
Close files opened by lock_file() before unlinking.
rebase -i: move help to end of todo file
Johannes Sixt (14):
git-gui: Change main window layout to support wider screens
Give git-am back the ability to add Signed-off-by lines.
t5300-pack-object.sh: Split the big verify-pack test into smaller
parts.
t7501-commit.sh: Not all seds understand option -i
t5302-pack-index: Skip tests of 64-bit offsets if necessary.
Skip t3902-quoted.sh if the file system does not support funny
names.
Use is_absolute_path() in sha1_file.c.
Move #include <sys/select.h> and <sys/ioctl.h> to
git-compat-util.h.
builtin run_command: do not exit with -1.
Allow a relative builtin template directory.
Introduce git_etc_gitconfig() that encapsulates access of
ETC_GITCONFIG.
Allow ETC_GITCONFIG to be a relative path.
fetch-pack: Prepare for a side-band demultiplexer in a thread.
Flush progress message buffer in display().
Junio C Hamano (20):
git-gui po/README: Guide to translators
git-gui: Update Japanese strings (part 2)
scripts: Add placeholders for OPTIONS_SPEC
git-rev-parse --parseopt
git-sh-setup: fix parseopt `eval` string underquoting
send-pack: segfault fix on forced push
git-am: -i does not take a string parameter.
git-bisect: war on "sed"
git-bisect: use update-ref to mark good/bad commits
git-bisect: modernize branch shuffling hack
Draft release notes: fix clean.requireForce description
Update draft release notes for 1.5.4
git-clean: Fix error message if clean.requireForce is not set.
git-compat-util.h: auto-adjust to compiler support of FLEX_ARRAY a
bit better
Fix "quote" misconversion for rewrite diff output.
Make test scripts executable.
Addendum to "MaintNotes"
t4119: correct overeager war-on-whitespace
Deprecate peek-remote
Update draft release notes for 1.5.4
Kirill (1):
Updated Russian translation.
Konstantin V. Arkhipov (1):
git-svn's dcommit must use subversion's config
Linus Torvalds (6):
Simplify topo-sort logic
Add "--early-output" log flag for interactive GUI use
Enhance --early-output format
revision walker: mini clean-up
Fix rev-list when showing objects involving submodules
Fix parent rewriting in --early-output
Michele Ballabio (4):
git-gui: remove dots in some UI strings
git-gui: add some strings to translation
git-gui: fix typo in lib/blame.tcl
git-gui: update Italian translation
Mike Hommey (1):
Do git reset --hard HEAD when using git rebase --skip
Miklos Vajna (1):
Hungarian translation of git-gui
Nanako Shiraishi (2):
Japanese translation of git-gui
git-gui: Update Japanese strings
Nicolas Pitre (1):
rehabilitate some t5302 tests on 32-bit off_t machines
Paolo Ciarrocchi (1):
Italian translation of git-gui
Pierre Habouzit (17):
Add a parseopt mode to git-rev-parse to bring parse-options to
shell scripts.
Update git-sh-setup(1) to allow transparent use of git-rev-parse
--parseopt
Migrate git-clean.sh to use git-rev-parse --parseopt.
Migrate git-clone to use git-rev-parse --parseopt
Migrate git-am.sh to use git-rev-parse --parseopt
Migrate git-merge.sh to use git-rev-parse --parseopt
Migrate git-instaweb.sh to use git-rev-parse --parseopt
Migrate git-checkout.sh to use git-rev-parse --parseopt
--keep-dashdash
Migrate git-quiltimport.sh to use git-rev-parse --parseopt
Migrate git-repack.sh to use git-rev-parse --parseopt
sh-setup: don't let eval output to be shell-expanded.
parse-options new features.
Use OPT_SET_INT and OPT_BIT in builtin-branch
Use OPT_BIT in builtin-for-each-ref
Use OPT_BIT in builtin-pack-refs
Make the diff_options bitfields be an unsigned with explicit masks.
Reorder diff_opt_parse options more logically per topics.
Shawn Bohrer (2):
Make git-clean a builtin
Teach git clean to use setup_standard_excludes()
Shawn O. Pearce (57):
git-gui: Locate the library directory early during startup
git-gui: Initialize Tcl's msgcat library for internationalization
git-gui: Update po/README as symlink process is not necessary
git-gui: Correct stock message for 'Invalid font specified in %s'
git-gui: Quiet the msgfmt part of the make process
git-gui: Ensure msgfmt failure stops GNU make
git-gui: Mark revision chooser tooltip for translation
git-gui: Localize commit/author dates when displaying them
git-gui: Support context-sensitive i18n
git-gui: Document the new i18n context support
git-gui: Make the tree browser also use lightgray selection
git-gui: Paper bag fix missing translated strings
git-gui: Fix missing i18n markup in push/fetch windows
git-gui: Support native Win32 Tcl/Tk under Cygwin
git-gui: Refactor some UI init to occur earlier
git-gui: Allow users to choose/create/clone a repository
git-gui: Avoid console scrollbars unless they are necessary
git-gui: Don't bother showing OS error message about hardlinks
git-gui: Keep the UI responsive while counting objects in clone
git-gui: Copy objects/info/alternates during standard clone
git-gui: Don't delete console window namespaces too early
git-gui: Don't delete scrollbars in console windows
git-gui: Switch the git-gui logo to Henrik Nyh's logo
git-gui: Make the status bar easier to read in the setup wizard
git-gui: Use Henrik Nyh's git logo icon on Windows systems
git-gui: Support a native Mac OS X application bundle
git-gui: Refer to ourselves as "Git Gui" and not "git-gui"
git-gui: Allow forced push into remote repository
git-gui: Refactor Henrik Nyh's logo into its own procedure
git-gui: Refactor about dialog code into its own module
git-gui: Include our Git logo in the about dialog
git-gui: Use progress meter in the status bar during index updates
git-gui: Consolidate the Fetch and Push menus into a Remote menu
git-gui: Bind Cmd-, to Preferences on Mac OS X
git-gui: Shorten the staged/unstaged changes title bar text
git-gui: Updated po strings based on current sources
git-gui: Move load_config procedure below git-version selection
git-gui: Refactor git-config --list parsing
git-gui: Support LFs embedded in config file values
git-gui: Change repository browser radio buttons to hyperlinks
git-gui: Offer repository management features in menu bar
git-gui: Fix bind errors when switching repository chooser panels
git-gui: Disable the text widget in the repository chooser
git-gui: Bind n/c/o accelerators in repository chooser
git-gui: Ensure copyright message is correctly read as UTF-8
git-gui: Use proper Windows shortcuts instead of bat files
git-gui: Support cloning Cygwin based work-dirs
git-gui: Collapse $env(HOME) to ~/ in recent repositories on
Windows
git-gui: Honor a config.mak in git-gui's top level
git-gui: Paper bag fix the global config parsing
git-gui: Make sure we get errors from git-update-index
git-gui: Protect against bad translation strings
git-gui: Allow users to set font weights to bold
Reteach builtin-ls-remote to understand remotes
git-gui: Bind Meta-T for "Stage To Commit" menu action
Fix warning about bitfield in struct ref
git-gui 0.9.0
Shun Kei Leung (1):
git-p4: Fix typo in --detect-labels
Simon Hausmann (1):
git-p4: Fix direct import from perforce after fetching changes
through git from origin
Steffen Prohaska (4):
git-gui: add directory git-gui is located in to PATH (on Windows)
git-gui: set NO_MSGFMT to force using pure tcl replacement in
msysgit
git-gui: add mingw specific startup wrapper
git-gui: offer a list of recent repositories on startup
Thomas Harning (1):
git-merge-ours: make it a builtin.
Wincent Colaiuta (3):
Further clarify clean.requireForce changes
Authenticate only once in git-send-email
Refactor patch_update_cmd
Xudong Guan (2):
Initial Chinese translation for git-gui
git-gui: Added initial version of po/glossary/zh_cn.po
^ permalink raw reply
* [StGit PATCH 00/10] Infrastructure rewrite series
From: Karl Hasselström @ 2007-11-25 20:50 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git, David Kågedal
I've expanded on the infrastructure rewrite series. As of now, clean,
applied, unapplied, goto, uncommit, and the new command coalesce have
been converted.
The main new development is support for index and worktree operations,
so that it can do conflicting merges. This made it possible to convert
"stg goto", but the main practical attraction is the vastly improved
"stg coalesce", which can now take an arbitrary list of patches and
reorder them so that they can be joined to one big patch. It will do
so entirely automatically if the merges resolve automatically, and if
they don't it will pretend to have done a series of pops, pushes, and
deletes, and leave the user to manually resolve the first conflicting
push.
The implementation of coalesce -- particularly the ability to fail
gracefully on conflicts at any intermediate step -- is helped a lot by
some new cool transaction stuff.
Available from
git://repo.or.cz/stgit/kha.git experimental
---
Karl Hasselström (10):
Convert "stg uncommit" to the new infrastructure
Let "stg goto" use the new infrastructure
Let "stg clean" use the new transaction primitives
Teach the new infrastructure about the index and worktree
Let "stg applied" and "stg unapplied" use the new infrastructure
Add "stg coalesce"
Let "stg clean" use the new infrastructure
Upgrade older stacks to newest version
Write metadata files used by the old infrastructure
New StGit core infrastructure: repository operations
contrib/stgit-completion.bash | 2
setup.py | 2
stgit/commands/applied.py | 27 +--
stgit/commands/clean.py | 49 ++---
stgit/commands/coalesce.py | 109 ++++++++++++
stgit/commands/common.py | 10 +
stgit/commands/goto.py | 52 ++----
stgit/commands/unapplied.py | 23 +-
stgit/commands/uncommit.py | 79 ++++----
stgit/lib/__init__.py | 18 ++
stgit/lib/git.py | 383 +++++++++++++++++++++++++++++++++++++++++
stgit/lib/stack.py | 168 ++++++++++++++++++
stgit/lib/stackupgrade.py | 96 ++++++++++
stgit/lib/transaction.py | 194 +++++++++++++++++++++
stgit/main.py | 2
stgit/stack.py | 100 +----------
stgit/utils.py | 24 +++
t/t2600-coalesce.sh | 31 +++
18 files changed, 1136 insertions(+), 233 deletions(-)
create mode 100644 stgit/commands/coalesce.py
create mode 100644 stgit/lib/__init__.py
create mode 100644 stgit/lib/git.py
create mode 100644 stgit/lib/stack.py
create mode 100644 stgit/lib/stackupgrade.py
create mode 100644 stgit/lib/transaction.py
create mode 100755 t/t2600-coalesce.sh
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle
^ permalink raw reply
* [StGit PATCH 02/10] Write metadata files used by the old infrastructure
From: Karl Hasselström @ 2007-11-25 20:51 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git, David Kågedal
In-Reply-To: <20071125203717.7823.70046.stgit@yoghurt>
The new infrastructure doesn't use them, but they're needed to support
the old infrastructure during the transition when both of them are in
use.
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
stgit/lib/stack.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 49 insertions(+), 3 deletions(-)
diff --git a/stgit/lib/stack.py b/stgit/lib/stack.py
index d5bd488..5a34592 100644
--- a/stgit/lib/stack.py
+++ b/stgit/lib/stack.py
@@ -7,15 +7,61 @@ class Patch(object):
self.__stack = stack
self.__name = name
name = property(lambda self: self.__name)
+ @property
def __ref(self):
return 'refs/patches/%s/%s' % (self.__stack.name, self.__name)
@property
+ def __log_ref(self):
+ return self.__ref + '.log'
+ @property
def commit(self):
- return self.__stack.repository.refs.get(self.__ref())
+ return self.__stack.repository.refs.get(self.__ref)
+ @property
+ def __compat_dir(self):
+ return os.path.join(self.__stack.directory, 'patches', self.__name)
+ def __write_compat_files(self, new_commit, msg):
+ """Write files used by the old infrastructure."""
+ def write(name, val, multiline = False):
+ fn = os.path.join(self.__compat_dir, name)
+ if val:
+ utils.write_string(fn, val, multiline)
+ elif os.path.isfile(fn):
+ os.remove(fn)
+ def write_patchlog():
+ try:
+ old_log = [self.__stack.repository.refs.get(self.__log_ref)]
+ except KeyError:
+ old_log = []
+ cd = git.Commitdata(tree = new_commit.data.tree, parents = old_log,
+ message = '%s\t%s' % (msg, new_commit.sha1))
+ c = self.__stack.repository.commit(cd)
+ self.__stack.repository.refs.set(self.__log_ref, c, msg)
+ return c
+ d = new_commit.data
+ write('authname', d.author.name)
+ write('authemail', d.author.email)
+ write('authdate', d.author.date)
+ write('commname', d.committer.name)
+ write('commemail', d.committer.email)
+ write('description', d.message)
+ write('log', write_patchlog().sha1)
+ try:
+ old_commit_sha1 = self.commit
+ except KeyError:
+ old_commit_sha1 = None
+ write('top.old', old_commit_sha1)
+ def __delete_compat_files(self):
+ if os.path.isdir(self.__compat_dir):
+ for f in os.listdir(self.__compat_dir):
+ os.remove(os.path.join(self.__compat_dir, f))
+ os.rmdir(self.__compat_dir)
+ self.__stack.repository.refs.delete(self.__log_ref)
def set_commit(self, commit, msg):
- self.__stack.repository.refs.set(self.__ref(), commit, msg)
+ self.__write_compat_files(commit, msg)
+ self.__stack.repository.refs.set(self.__ref, commit, msg)
def delete(self):
- self.__stack.repository.refs.delete(self.__ref())
+ self.__delete_compat_files()
+ self.__stack.repository.refs.delete(self.__ref)
def is_applied(self):
return self.name in self.__stack.patchorder.applied
def is_empty(self):
^ permalink raw reply related
* [StGit PATCH 01/10] New StGit core infrastructure: repository operations
From: Karl Hasselström @ 2007-11-25 20:51 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git, David Kågedal
In-Reply-To: <20071125203717.7823.70046.stgit@yoghurt>
This is the first part of the New and Improved StGit core
infrastructure. It has functions for manipulating the git repository
(commits, refs, and so on), but doesn't yet touch the index or
worktree.
Currently not used by anything.
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
setup.py | 2
stgit/lib/__init__.py | 18 +++
stgit/lib/git.py | 253 ++++++++++++++++++++++++++++++++++++++++++++++
stgit/lib/stack.py | 120 ++++++++++++++++++++++
stgit/lib/transaction.py | 79 ++++++++++++++
stgit/utils.py | 13 ++
6 files changed, 484 insertions(+), 1 deletions(-)
create mode 100644 stgit/lib/__init__.py
create mode 100644 stgit/lib/git.py
create mode 100644 stgit/lib/stack.py
create mode 100644 stgit/lib/transaction.py
diff --git a/setup.py b/setup.py
index cf5b1da..cad8f7e 100755
--- a/setup.py
+++ b/setup.py
@@ -14,7 +14,7 @@ setup(name = 'stgit',
description = 'Stacked GIT',
long_description = 'Push/pop utility on top of GIT',
scripts = ['stg'],
- packages = ['stgit', 'stgit.commands'],
+ packages = ['stgit', 'stgit.commands', 'stgit.lib'],
data_files = [('share/stgit/templates', glob.glob('templates/*.tmpl')),
('share/stgit/examples', glob.glob('examples/*.tmpl')),
('share/stgit/examples', ['examples/gitconfig']),
diff --git a/stgit/__init__.py b/stgit/lib/__init__.py
similarity index 88%
copy from stgit/__init__.py
copy to stgit/lib/__init__.py
index 4b03e3a..45eb307 100644
--- a/stgit/__init__.py
+++ b/stgit/lib/__init__.py
@@ -1,5 +1,7 @@
+# -*- coding: utf-8 -*-
+
__copyright__ = """
-Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
+Copyright (C) 2007, Karl Hasselström <kha@treskal.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
diff --git a/stgit/lib/git.py b/stgit/lib/git.py
new file mode 100644
index 0000000..120ea35
--- /dev/null
+++ b/stgit/lib/git.py
@@ -0,0 +1,253 @@
+import os, os.path, re
+from stgit import exception, run, utils
+
+class RepositoryException(exception.StgException):
+ pass
+
+class DetachedHeadException(RepositoryException):
+ def __init__(self):
+ RepositoryException.__init__(self, 'Not on any branch')
+
+class Repr(object):
+ def __repr__(self):
+ return str(self)
+
+class NoValue(object):
+ pass
+
+def make_defaults(defaults):
+ def d(val, attr):
+ if val != NoValue:
+ return val
+ elif defaults != NoValue:
+ return getattr(defaults, attr)
+ else:
+ return None
+ return d
+
+class Person(Repr):
+ """Immutable."""
+ def __init__(self, name = NoValue, email = NoValue,
+ date = NoValue, defaults = NoValue):
+ d = make_defaults(defaults)
+ self.__name = d(name, 'name')
+ self.__email = d(email, 'email')
+ self.__date = d(date, 'date')
+ name = property(lambda self: self.__name)
+ email = property(lambda self: self.__email)
+ date = property(lambda self: self.__date)
+ def set_name(self, name):
+ return type(self)(name = name, defaults = self)
+ def set_email(self, email):
+ return type(self)(email = email, defaults = self)
+ def set_date(self, date):
+ return type(self)(date = date, defaults = self)
+ def __str__(self):
+ return '%s <%s> %s' % (self.name, self.email, self.date)
+ @classmethod
+ def parse(cls, s):
+ m = re.match(r'^([^<]*)<([^>]*)>\s+(\d+\s+[+-]\d{4})$', s)
+ assert m
+ name = m.group(1).strip()
+ email = m.group(2)
+ date = m.group(3)
+ return cls(name, email, date)
+
+class Tree(Repr):
+ """Immutable."""
+ def __init__(self, sha1):
+ self.__sha1 = sha1
+ sha1 = property(lambda self: self.__sha1)
+ def __str__(self):
+ return 'Tree<%s>' % self.sha1
+
+class Commitdata(Repr):
+ """Immutable."""
+ def __init__(self, tree = NoValue, parents = NoValue, author = NoValue,
+ committer = NoValue, message = NoValue, defaults = NoValue):
+ d = make_defaults(defaults)
+ self.__tree = d(tree, 'tree')
+ self.__parents = d(parents, 'parents')
+ self.__author = d(author, 'author')
+ self.__committer = d(committer, 'committer')
+ self.__message = d(message, 'message')
+ tree = property(lambda self: self.__tree)
+ parents = property(lambda self: self.__parents)
+ @property
+ def parent(self):
+ assert len(self.__parents) == 1
+ return self.__parents[0]
+ author = property(lambda self: self.__author)
+ committer = property(lambda self: self.__committer)
+ message = property(lambda self: self.__message)
+ def set_tree(self, tree):
+ return type(self)(tree = tree, defaults = self)
+ def set_parents(self, parents):
+ return type(self)(parents = parents, defaults = self)
+ def add_parent(self, parent):
+ return type(self)(parents = list(self.parents or []) + [parent],
+ defaults = self)
+ def set_parent(self, parent):
+ return self.set_parents([parent])
+ def set_author(self, author):
+ return type(self)(author = author, defaults = self)
+ def set_committer(self, committer):
+ return type(self)(committer = committer, defaults = self)
+ def set_message(self, message):
+ return type(self)(message = message, defaults = self)
+ def __str__(self):
+ if self.tree == None:
+ tree = None
+ else:
+ tree = self.tree.sha1
+ if self.parents == None:
+ parents = None
+ else:
+ parents = [p.sha1 for p in self.parents]
+ return ('Commitdata<tree: %s, parents: %s, author: %s,'
+ ' committer: %s, message: "%s">'
+ ) % (tree, parents, self.author, self.committer, self.message)
+ @classmethod
+ def parse(cls, repository, s):
+ cd = cls()
+ lines = list(s.splitlines(True))
+ for i in xrange(len(lines)):
+ line = lines[i].strip()
+ if not line:
+ return cd.set_message(''.join(lines[i+1:]))
+ key, value = line.split(None, 1)
+ if key == 'tree':
+ cd = cd.set_tree(repository.get_tree(value))
+ elif key == 'parent':
+ cd = cd.add_parent(repository.get_commit(value))
+ elif key == 'author':
+ cd = cd.set_author(Person.parse(value))
+ elif key == 'committer':
+ cd = cd.set_committer(Person.parse(value))
+ else:
+ assert False
+ assert False
+
+class Commit(Repr):
+ """Immutable."""
+ def __init__(self, repository, sha1):
+ self.__sha1 = sha1
+ self.__repository = repository
+ self.__data = None
+ sha1 = property(lambda self: self.__sha1)
+ @property
+ def data(self):
+ if self.__data == None:
+ self.__data = Commitdata.parse(
+ self.__repository,
+ self.__repository.cat_object(self.sha1))
+ return self.__data
+ def __str__(self):
+ return 'Commit<sha1: %s, data: %s>' % (self.sha1, self.__data)
+
+class Refs(object):
+ def __init__(self, repository):
+ self.__repository = repository
+ self.__refs = None
+ def __cache_refs(self):
+ self.__refs = {}
+ for line in self.__repository.run(['git', 'show-ref']).output_lines():
+ m = re.match(r'^([0-9a-f]{40})\s+(\S+)$', line)
+ sha1, ref = m.groups()
+ self.__refs[ref] = sha1
+ def get(self, ref):
+ """Throws KeyError if ref doesn't exist."""
+ if self.__refs == None:
+ self.__cache_refs()
+ return self.__repository.get_commit(self.__refs[ref])
+ def set(self, ref, commit, msg):
+ if self.__refs == None:
+ self.__cache_refs()
+ old_sha1 = self.__refs.get(ref, '0'*40)
+ new_sha1 = commit.sha1
+ if old_sha1 != new_sha1:
+ self.__repository.run(['git', 'update-ref', '-m', msg,
+ ref, new_sha1, old_sha1]).no_output()
+ self.__refs[ref] = new_sha1
+ def delete(self, ref):
+ if self.__refs == None:
+ self.__cache_refs()
+ self.__repository.run(['git', 'update-ref',
+ '-d', ref, self.__refs[ref]]).no_output()
+ del self.__refs[ref]
+
+class ObjectCache(object):
+ """Cache for Python objects, for making sure that we create only one
+ Python object per git object."""
+ def __init__(self, create):
+ self.__objects = {}
+ self.__create = create
+ def __getitem__(self, name):
+ if not name in self.__objects:
+ self.__objects[name] = self.__create(name)
+ return self.__objects[name]
+ def __contains__(self, name):
+ return name in self.__objects
+ def __setitem__(self, name, val):
+ assert not name in self.__objects
+ self.__objects[name] = val
+
+class RunWithEnv(object):
+ def run(self, args, env = {}):
+ return run.Run(*args).env(utils.add_dict(self.env, env))
+
+class Repository(RunWithEnv):
+ def __init__(self, directory):
+ self.__git_dir = directory
+ self.__refs = Refs(self)
+ self.__trees = ObjectCache(lambda sha1: Tree(sha1))
+ self.__commits = ObjectCache(lambda sha1: Commit(self, sha1))
+ env = property(lambda self: { 'GIT_DIR': self.__git_dir })
+ @classmethod
+ def default(cls):
+ """Return the default repository."""
+ try:
+ return cls(run.Run('git', 'rev-parse', '--git-dir'
+ ).output_one_line())
+ except run.RunException:
+ raise RepositoryException('Cannot find git repository')
+ directory = property(lambda self: self.__git_dir)
+ refs = property(lambda self: self.__refs)
+ def cat_object(self, sha1):
+ return self.run(['git', 'cat-file', '-p', sha1]).raw_output()
+ def rev_parse(self, rev):
+ try:
+ return self.get_commit(self.run(
+ ['git', 'rev-parse', '%s^{commit}' % rev]
+ ).output_one_line())
+ except run.RunException:
+ raise RepositoryException('%s: No such revision' % rev)
+ def get_tree(self, sha1):
+ return self.__trees[sha1]
+ def get_commit(self, sha1):
+ return self.__commits[sha1]
+ def commit(self, commitdata):
+ c = ['git', 'commit-tree', commitdata.tree.sha1]
+ for p in commitdata.parents:
+ c.append('-p')
+ c.append(p.sha1)
+ env = {}
+ for p, v1 in ((commitdata.author, 'AUTHOR'),
+ (commitdata.committer, 'COMMITTER')):
+ if p != None:
+ for attr, v2 in (('name', 'NAME'), ('email', 'EMAIL'),
+ ('date', 'DATE')):
+ if getattr(p, attr) != None:
+ env['GIT_%s_%s' % (v1, v2)] = getattr(p, attr)
+ sha1 = self.run(c, env = env).raw_input(commitdata.message
+ ).output_one_line()
+ return self.get_commit(sha1)
+ @property
+ def head(self):
+ try:
+ return self.run(['git', 'symbolic-ref', '-q', 'HEAD']
+ ).output_one_line()
+ except run.RunException:
+ raise DetachedHeadException()
+ def set_head(self, ref, msg):
+ self.run(['git', 'symbolic-ref', '-m', msg, 'HEAD', ref]).no_output()
diff --git a/stgit/lib/stack.py b/stgit/lib/stack.py
new file mode 100644
index 0000000..d5bd488
--- /dev/null
+++ b/stgit/lib/stack.py
@@ -0,0 +1,120 @@
+import os.path
+from stgit import exception, utils
+from stgit.lib import git
+
+class Patch(object):
+ def __init__(self, stack, name):
+ self.__stack = stack
+ self.__name = name
+ name = property(lambda self: self.__name)
+ def __ref(self):
+ return 'refs/patches/%s/%s' % (self.__stack.name, self.__name)
+ @property
+ def commit(self):
+ return self.__stack.repository.refs.get(self.__ref())
+ def set_commit(self, commit, msg):
+ self.__stack.repository.refs.set(self.__ref(), commit, msg)
+ def delete(self):
+ self.__stack.repository.refs.delete(self.__ref())
+ def is_applied(self):
+ return self.name in self.__stack.patchorder.applied
+ def is_empty(self):
+ c = self.commit
+ return c.data.tree == c.data.parent.data.tree
+
+class PatchOrder(object):
+ """Keeps track of patch order, and which patches are applied.
+ Works with patch names, not actual patches."""
+ __list_order = [ 'applied', 'unapplied' ]
+ def __init__(self, stack):
+ self.__stack = stack
+ self.__lists = {}
+ def __read_file(self, fn):
+ return tuple(utils.read_strings(
+ os.path.join(self.__stack.directory, fn)))
+ def __write_file(self, fn, val):
+ utils.write_strings(os.path.join(self.__stack.directory, fn), val)
+ def __get_list(self, name):
+ if not name in self.__lists:
+ self.__lists[name] = self.__read_file(name)
+ return self.__lists[name]
+ def __set_list(self, name, val):
+ val = tuple(val)
+ if val != self.__lists.get(name, None):
+ self.__lists[name] = val
+ self.__write_file(name, val)
+ applied = property(lambda self: self.__get_list('applied'),
+ lambda self, val: self.__set_list('applied', val))
+ unapplied = property(lambda self: self.__get_list('unapplied'),
+ lambda self, val: self.__set_list('unapplied', val))
+
+class Patches(object):
+ """Creates Patch objects."""
+ def __init__(self, stack):
+ self.__stack = stack
+ def create_patch(name):
+ p = Patch(self.__stack, name)
+ p.commit # raise exception if the patch doesn't exist
+ return p
+ self.__patches = git.ObjectCache(create_patch) # name -> Patch
+ def exists(self, name):
+ try:
+ self.get(name)
+ return True
+ except KeyError:
+ return False
+ def get(self, name):
+ return self.__patches[name]
+ def new(self, name, commit, msg):
+ assert not name in self.__patches
+ p = Patch(self.__stack, name)
+ p.set_commit(commit, msg)
+ self.__patches[name] = p
+ return p
+
+class Stack(object):
+ def __init__(self, repository, name):
+ self.__repository = repository
+ self.__name = name
+ try:
+ self.head
+ except KeyError:
+ raise exception.StgException('%s: no such branch' % name)
+ self.__patchorder = PatchOrder(self)
+ self.__patches = Patches(self)
+ name = property(lambda self: self.__name)
+ repository = property(lambda self: self.__repository)
+ patchorder = property(lambda self: self.__patchorder)
+ patches = property(lambda self: self.__patches)
+ @property
+ def directory(self):
+ return os.path.join(self.__repository.directory, 'patches', self.__name)
+ def __ref(self):
+ return 'refs/heads/%s' % self.__name
+ @property
+ def head(self):
+ return self.__repository.refs.get(self.__ref())
+ def set_head(self, commit, msg):
+ self.__repository.refs.set(self.__ref(), commit, msg)
+ @property
+ def base(self):
+ if self.patchorder.applied:
+ return self.patches.get(self.patchorder.applied[0]
+ ).commit.data.parent
+ else:
+ return self.head
+
+class Repository(git.Repository):
+ def __init__(self, *args, **kwargs):
+ git.Repository.__init__(self, *args, **kwargs)
+ self.__stacks = {} # name -> Stack
+ @property
+ def current_branch(self):
+ return utils.strip_leading('refs/heads/', self.head)
+ @property
+ def current_stack(self):
+ return self.get_stack(self.current_branch)
+ def get_stack(self, name):
+ if not name in self.__stacks:
+ self.__stacks[name] = Stack(self, name)
+ return self.__stacks[name]
diff --git a/stgit/lib/transaction.py b/stgit/lib/transaction.py
new file mode 100644
index 0000000..991e64e
--- /dev/null
+++ b/stgit/lib/transaction.py
@@ -0,0 +1,79 @@
+from stgit import exception
+from stgit.out import *
+
+class TransactionException(exception.StgException):
+ pass
+
+def print_current_patch(old_applied, new_applied):
+ def now_at(pn):
+ out.info('Now at patch "%s"' % pn)
+ if not old_applied and not new_applied:
+ pass
+ elif not old_applied:
+ now_at(new_applied[-1])
+ elif not new_applied:
+ out.info('No patch applied')
+ elif old_applied[-1] == new_applied[-1]:
+ pass
+ else:
+ now_at(new_applied[-1])
+
+class StackTransaction(object):
+ def __init__(self, stack, msg):
+ self.__stack = stack
+ self.__msg = msg
+ self.__patches = {}
+ self.__applied = list(self.__stack.patchorder.applied)
+ self.__unapplied = list(self.__stack.patchorder.unapplied)
+ def __set_patches(self, val):
+ self.__patches = dict(val)
+ patches = property(lambda self: self.__patches, __set_patches)
+ def __set_applied(self, val):
+ self.__applied = list(val)
+ applied = property(lambda self: self.__applied, __set_applied)
+ def __set_unapplied(self, val):
+ self.__unapplied = list(val)
+ unapplied = property(lambda self: self.__unapplied, __set_unapplied)
+ def __check_consistency(self):
+ remaining = set(self.__applied + self.__unapplied)
+ for pn, commit in self.__patches.iteritems():
+ if commit == None:
+ assert self.__stack.patches.exists(pn)
+ else:
+ assert pn in remaining
+ def run(self):
+ self.__check_consistency()
+
+ # Get new head commit.
+ if self.__applied:
+ top_patch = self.__applied[-1]
+ try:
+ new_head = self.__patches[top_patch]
+ except KeyError:
+ new_head = self.__stack.patches.get(top_patch).commit
+ else:
+ new_head = self.__stack.base
+
+ # Set branch head.
+ if new_head == self.__stack.head:
+ pass # same commit: OK
+ elif new_head.data.tree == self.__stack.head.data.tree:
+ pass # same tree: OK
+ else:
+ # We can't handle this case yet.
+ raise TransactionException('Error: HEAD tree changed')
+ self.__stack.set_head(new_head, self.__msg)
+
+ # Write patches.
+ for pn, commit in self.__patches.iteritems():
+ if self.__stack.patches.exists(pn):
+ p = self.__stack.patches.get(pn)
+ if commit == None:
+ p.delete()
+ else:
+ p.set_commit(commit, self.__msg)
+ else:
+ self.__stack.patches.new(pn, commit, self.__msg)
+ print_current_patch(self.__stack.patchorder.applied, self.__applied)
+ self.__stack.patchorder.applied = self.__applied
+ self.__stack.patchorder.unapplied = self.__unapplied
diff --git a/stgit/utils.py b/stgit/utils.py
index 3a480c0..b3f6232 100644
--- a/stgit/utils.py
+++ b/stgit/utils.py
@@ -256,3 +256,16 @@ def add_sign_line(desc, sign_str, name, email):
if not any(s in desc for s in ['\nSigned-off-by:', '\nAcked-by:']):
desc = desc + '\n'
return '%s\n%s\n' % (desc, sign_str)
+
+def strip_leading(prefix, s):
+ """Strip leading prefix from a string. Blow up if the prefix isn't
+ there."""
+ assert s.startswith(prefix)
+ return s[len(prefix):]
+
+def add_dict(d1, d2):
+ """Return a new dict with the contents of both d1 and d2. In case of
+ conflicting mappings, d2 takes precedence."""
+ d = dict(d1)
+ d.update(d2)
+ return d
^ permalink raw reply related
* [StGit PATCH 03/10] Upgrade older stacks to newest version
From: Karl Hasselström @ 2007-11-25 20:51 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git, David Kågedal
In-Reply-To: <20071125203717.7823.70046.stgit@yoghurt>
This is of course needed by the new infrastructure as well. So break
it out into its own file, where it can be used by both new and old
infrastructure. This has the added benefit of making it easy to see
that the upgrade code doesn't depend on anything it shouldn't.
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
stgit/lib/git.py | 7 +++
stgit/lib/stack.py | 3 +
stgit/lib/stackupgrade.py | 96 +++++++++++++++++++++++++++++++++++++++++++
stgit/stack.py | 100 +++------------------------------------------
4 files changed, 112 insertions(+), 94 deletions(-)
create mode 100644 stgit/lib/stackupgrade.py
diff --git a/stgit/lib/git.py b/stgit/lib/git.py
index 120ea35..c4011f9 100644
--- a/stgit/lib/git.py
+++ b/stgit/lib/git.py
@@ -160,6 +160,13 @@ class Refs(object):
if self.__refs == None:
self.__cache_refs()
return self.__repository.get_commit(self.__refs[ref])
+ def exists(self, ref):
+ try:
+ self.get(ref)
+ except KeyError:
+ return False
+ else:
+ return True
def set(self, ref, commit, msg):
if self.__refs == None:
self.__cache_refs()
diff --git a/stgit/lib/stack.py b/stgit/lib/stack.py
index 5a34592..8fc8b08 100644
--- a/stgit/lib/stack.py
+++ b/stgit/lib/stack.py
@@ -1,6 +1,6 @@
import os.path
from stgit import exception, utils
-from stgit.lib import git
+from stgit.lib import git, stackupgrade
class Patch(object):
def __init__(self, stack, name):
@@ -128,6 +128,7 @@ class Stack(object):
raise exception.StgException('%s: no such branch' % name)
self.__patchorder = PatchOrder(self)
self.__patches = Patches(self)
+ stackupgrade.update_to_current_format_version(repository, name)
name = property(lambda self: self.__name)
repository = property(lambda self: self.__repository)
patchorder = property(lambda self: self.__patchorder)
diff --git a/stgit/lib/stackupgrade.py b/stgit/lib/stackupgrade.py
new file mode 100644
index 0000000..00bfdf0
--- /dev/null
+++ b/stgit/lib/stackupgrade.py
@@ -0,0 +1,96 @@
+import os.path
+from stgit import utils
+from stgit.out import out
+from stgit.config import config
+
+# The current StGit metadata format version.
+FORMAT_VERSION = 2
+
+def format_version_key(branch):
+ return 'branch.%s.stgit.stackformatversion' % branch
+
+def update_to_current_format_version(repository, branch):
+ """Update a potentially older StGit directory structure to the latest
+ version. Note: This function should depend as little as possible
+ on external functions that may change during a format version
+ bump, since it must remain able to process older formats."""
+
+ branch_dir = os.path.join(repository.directory, 'patches', branch)
+ key = format_version_key(branch)
+ old_key = 'branch.%s.stgitformatversion' % branch
+ def get_format_version():
+ """Return the integer format version number, or None if the
+ branch doesn't have any StGit metadata at all, of any version."""
+ fv = config.get(key)
+ ofv = config.get(old_key)
+ if fv:
+ # Great, there's an explicitly recorded format version
+ # number, which means that the branch is initialized and
+ # of that exact version.
+ return int(fv)
+ elif ofv:
+ # Old name for the version info: upgrade it.
+ config.set(key, ofv)
+ config.unset(old_key)
+ return int(ofv)
+ elif os.path.isdir(os.path.join(branch_dir, 'patches')):
+ # There's a .git/patches/<branch>/patches dirctory, which
+ # means this is an initialized version 1 branch.
+ return 1
+ elif os.path.isdir(branch_dir):
+ # There's a .git/patches/<branch> directory, which means
+ # this is an initialized version 0 branch.
+ return 0
+ else:
+ # The branch doesn't seem to be initialized at all.
+ return None
+ def set_format_version(v):
+ out.info('Upgraded branch %s to format version %d' % (branch, v))
+ config.set(key, '%d' % v)
+ def mkdir(d):
+ if not os.path.isdir(d):
+ os.makedirs(d)
+ def rm(f):
+ if os.path.exists(f):
+ os.remove(f)
+ def rm_ref(ref):
+ if repository.refs.exists(ref):
+ repository.refs.delete(ref)
+
+ # Update 0 -> 1.
+ if get_format_version() == 0:
+ mkdir(os.path.join(branch_dir, 'trash'))
+ patch_dir = os.path.join(branch_dir, 'patches')
+ mkdir(patch_dir)
+ refs_base = 'refs/patches/%s' % branch
+ for patch in (file(os.path.join(branch_dir, 'unapplied')).readlines()
+ + file(os.path.join(branch_dir, 'applied')).readlines()):
+ patch = patch.strip()
+ os.rename(os.path.join(branch_dir, patch),
+ os.path.join(patch_dir, patch))
+ topfield = os.path.join(patch_dir, patch, 'top')
+ if os.path.isfile(topfield):
+ top = utils.read_string(topfield, False)
+ else:
+ top = None
+ if top:
+ repository.refs.set(refs_base + '/' + patch,
+ repository.get_commit(top), 'StGit upgrade')
+ set_format_version(1)
+
+ # Update 1 -> 2.
+ if get_format_version() == 1:
+ desc_file = os.path.join(branch_dir, 'description')
+ if os.path.isfile(desc_file):
+ desc = utils.read_string(desc_file)
+ if desc:
+ config.set('branch.%s.description' % branch, desc)
+ rm(desc_file)
+ rm(os.path.join(branch_dir, 'current'))
+ rm_ref('refs/bases/%s' % branch)
+ set_format_version(2)
+
+ # Make sure we're at the latest version.
+ if not get_format_version() in [None, FORMAT_VERSION]:
+ raise StackException('Branch %s is at format version %d, expected %d'
+ % (branch, get_format_version(), FORMAT_VERSION))
diff --git a/stgit/stack.py b/stgit/stack.py
index f93d842..29e92c9 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -28,7 +28,7 @@ from stgit.run import *
from stgit import git, basedir, templates
from stgit.config import config
from shutil import copyfile
-
+from stgit.lib import git as libgit, stackupgrade
# stack exception class
class StackException(StgException):
@@ -279,9 +279,6 @@ class Patch(StgitObject):
self._set_field('log', value)
self.__update_log_ref(value)
-# The current StGIT metadata format version.
-FORMAT_VERSION = 2
-
class PatchSet(StgitObject):
def __init__(self, name = None):
try:
@@ -349,7 +346,8 @@ class PatchSet(StgitObject):
def is_initialised(self):
"""Checks if series is already initialised
"""
- return bool(config.get(self.format_version_key()))
+ return config.get(stackupgrade.format_version_key(self.get_name())
+ ) != None
def shortlog(patches):
@@ -368,7 +366,8 @@ class Series(PatchSet):
# Update the branch to the latest format version if it is
# initialized, but don't touch it if it isn't.
- self.update_to_current_format_version()
+ stackupgrade.update_to_current_format_version(
+ libgit.Repository.default(), self.get_name())
self.__refs_base = 'refs/patches/%s' % self.get_name()
@@ -382,92 +381,6 @@ class Series(PatchSet):
# trash directory
self.__trash_dir = os.path.join(self._dir(), 'trash')
- def format_version_key(self):
- return 'branch.%s.stgit.stackformatversion' % self.get_name()
-
- def update_to_current_format_version(self):
- """Update a potentially older StGIT directory structure to the
- latest version. Note: This function should depend as little as
- possible on external functions that may change during a format
- version bump, since it must remain able to process older formats."""
-
- branch_dir = os.path.join(self._basedir(), 'patches', self.get_name())
- def get_format_version():
- """Return the integer format version number, or None if the
- branch doesn't have any StGIT metadata at all, of any version."""
- fv = config.get(self.format_version_key())
- ofv = config.get('branch.%s.stgitformatversion' % self.get_name())
- if fv:
- # Great, there's an explicitly recorded format version
- # number, which means that the branch is initialized and
- # of that exact version.
- return int(fv)
- elif ofv:
- # Old name for the version info, upgrade it
- config.set(self.format_version_key(), ofv)
- config.unset('branch.%s.stgitformatversion' % self.get_name())
- return int(ofv)
- elif os.path.isdir(os.path.join(branch_dir, 'patches')):
- # There's a .git/patches/<branch>/patches dirctory, which
- # means this is an initialized version 1 branch.
- return 1
- elif os.path.isdir(branch_dir):
- # There's a .git/patches/<branch> directory, which means
- # this is an initialized version 0 branch.
- return 0
- else:
- # The branch doesn't seem to be initialized at all.
- return None
- def set_format_version(v):
- out.info('Upgraded branch %s to format version %d' % (self.get_name(), v))
- config.set(self.format_version_key(), '%d' % v)
- def mkdir(d):
- if not os.path.isdir(d):
- os.makedirs(d)
- def rm(f):
- if os.path.exists(f):
- os.remove(f)
- def rm_ref(ref):
- if git.ref_exists(ref):
- git.delete_ref(ref)
-
- # Update 0 -> 1.
- if get_format_version() == 0:
- mkdir(os.path.join(branch_dir, 'trash'))
- patch_dir = os.path.join(branch_dir, 'patches')
- mkdir(patch_dir)
- refs_base = 'refs/patches/%s' % self.get_name()
- for patch in (file(os.path.join(branch_dir, 'unapplied')).readlines()
- + file(os.path.join(branch_dir, 'applied')).readlines()):
- patch = patch.strip()
- os.rename(os.path.join(branch_dir, patch),
- os.path.join(patch_dir, patch))
- topfield = os.path.join(patch_dir, patch, 'top')
- if os.path.isfile(topfield):
- top = read_string(topfield, False)
- else:
- top = None
- if top:
- git.set_ref(refs_base + '/' + patch, top)
- set_format_version(1)
-
- # Update 1 -> 2.
- if get_format_version() == 1:
- desc_file = os.path.join(branch_dir, 'description')
- if os.path.isfile(desc_file):
- desc = read_string(desc_file)
- if desc:
- config.set('branch.%s.description' % self.get_name(), desc)
- rm(desc_file)
- rm(os.path.join(branch_dir, 'current'))
- rm_ref('refs/bases/%s' % self.get_name())
- set_format_version(2)
-
- # Make sure we're at the latest version.
- if not get_format_version() in [None, FORMAT_VERSION]:
- raise StackException('Branch %s is at format version %d, expected %d'
- % (self.get_name(), get_format_version(), FORMAT_VERSION))
-
def __patch_name_valid(self, name):
"""Raise an exception if the patch name is not valid.
"""
@@ -620,7 +533,8 @@ class Series(PatchSet):
self.create_empty_field('applied')
self.create_empty_field('unapplied')
- config.set(self.format_version_key(), str(FORMAT_VERSION))
+ config.set(stackupgrade.format_version_key(self.get_name()),
+ str(stackupgrade.FORMAT_VERSION))
def rename(self, to_name):
"""Renames a series
^ permalink raw reply related
* [StGit PATCH 04/10] Let "stg clean" use the new infrastructure
From: Karl Hasselström @ 2007-11-25 20:51 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git, David Kågedal
In-Reply-To: <20071125203717.7823.70046.stgit@yoghurt>
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
stgit/commands/clean.py | 68 ++++++++++++++++++++++++----------------------
stgit/commands/common.py | 10 ++++++-
2 files changed, 44 insertions(+), 34 deletions(-)
diff --git a/stgit/commands/clean.py b/stgit/commands/clean.py
index c703418..bbea253 100644
--- a/stgit/commands/clean.py
+++ b/stgit/commands/clean.py
@@ -15,14 +15,10 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
-import sys, os
-from optparse import OptionParser, make_option
-
-from stgit.commands.common import *
-from stgit.utils import *
+from optparse import make_option
from stgit.out import *
-from stgit import stack, git
-
+from stgit.commands import common
+from stgit.lib import transaction
help = 'delete the empty patches in the series'
usage = """%prog [options]
@@ -31,7 +27,7 @@ Delete the empty patches in the whole series or only those applied or
unapplied. A patch is considered empty if the two commit objects
representing its boundaries refer to the same tree object."""
-directory = DirectoryGotoToplevel()
+directory = common.DirectoryHasRepositoryLib()
options = [make_option('-a', '--applied',
help = 'delete the empty applied patches',
action = 'store_true'),
@@ -40,18 +36,35 @@ options = [make_option('-a', '--applied',
action = 'store_true')]
-def __delete_empty(patches, applied):
- """Delete the empty patches
- """
- for p in patches:
- if crt_series.empty_patch(p):
- out.start('Deleting patch "%s"' % p)
- if applied and crt_series.patch_applied(p):
- crt_series.pop_patch(p)
- crt_series.delete_patch(p)
- out.done()
- elif applied and crt_series.patch_unapplied(p):
- crt_series.push_patch(p)
+def _clean(stack, clean_applied, clean_unapplied):
+ def deleting(pn):
+ out.info('Deleting empty patch %s' % pn)
+ trans = transaction.StackTransaction(stack, 'clean')
+ if clean_unapplied:
+ trans.unapplied = []
+ for pn in stack.patchorder.unapplied:
+ p = stack.patches.get(pn)
+ if p.is_empty():
+ trans.patches[pn] = None
+ deleting(pn)
+ else:
+ trans.unapplied.append(pn)
+ if clean_applied:
+ trans.applied = []
+ parent = stack.base
+ for pn in stack.patchorder.applied:
+ p = stack.patches.get(pn)
+ if p.is_empty():
+ trans.patches[pn] = None
+ deleting(pn)
+ else:
+ if parent != p.commit.data.parent:
+ parent = trans.patches[pn] = stack.repository.commit(
+ p.commit.data.set_parent(parent))
+ else:
+ parent = p.commit
+ trans.applied.append(pn)
+ trans.run()
def func(parser, options, args):
"""Delete the empty patches in the series
@@ -59,19 +72,8 @@ def func(parser, options, args):
if len(args) != 0:
parser.error('incorrect number of arguments')
- check_local_changes()
- check_conflicts()
- check_head_top_equal(crt_series)
-
if not (options.applied or options.unapplied):
options.applied = options.unapplied = True
- if options.applied:
- applied = crt_series.get_applied()
- __delete_empty(applied, True)
-
- if options.unapplied:
- unapplied = crt_series.get_unapplied()
- __delete_empty(unapplied, False)
-
- print_crt_patch(crt_series)
+ _clean(directory.repository.current_stack,
+ options.applied, options.unapplied)
diff --git a/stgit/commands/common.py b/stgit/commands/common.py
index 36202dd..6271572 100644
--- a/stgit/commands/common.py
+++ b/stgit/commands/common.py
@@ -27,7 +27,7 @@ from stgit.out import *
from stgit.run import *
from stgit import stack, git, basedir
from stgit.config import config, file_extensions
-
+from stgit.lib import stack as libstack
# Command exception class
class CmdException(StgException):
@@ -537,3 +537,11 @@ class DirectoryGotoToplevel(DirectoryInWorktree):
def setup(self):
DirectoryInWorktree.setup(self)
self.cd_to_topdir()
+
+class DirectoryHasRepositoryLib(_Directory):
+ """For commands that use the new infrastructure in stgit.lib.*."""
+ def __init__(self):
+ self.needs_current_series = False
+ def setup(self):
+ # This will throw an exception if we don't have a repository.
+ self.repository = libstack.Repository.default()
^ permalink raw reply related
* [StGit PATCH 06/10] Let "stg applied" and "stg unapplied" use the new infrastructure
From: Karl Hasselström @ 2007-11-25 20:51 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git, David Kågedal
In-Reply-To: <20071125203717.7823.70046.stgit@yoghurt>
This is a trivial change since these commands are so simple, but
because these are the commands used by t4000-upgrade, we now test that
the new infrastructure can upgrade old stacks.
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
stgit/commands/applied.py | 27 +++++++++++++--------------
stgit/commands/unapplied.py | 23 +++++++++++------------
2 files changed, 24 insertions(+), 26 deletions(-)
diff --git a/stgit/commands/applied.py b/stgit/commands/applied.py
index 45d0926..522425b 100644
--- a/stgit/commands/applied.py
+++ b/stgit/commands/applied.py
@@ -16,25 +16,21 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
-import sys, os
-from optparse import OptionParser, make_option
-
-from stgit.commands.common import *
-from stgit.utils import *
+from optparse import make_option
from stgit.out import *
-from stgit import stack, git
+from stgit.commands import common
help = 'print the applied patches'
usage = """%prog [options]
-List the patches from the series which were already pushed onto the
-stack. They are listed in the order in which they were pushed, the
+List the patches from the series which have already been pushed onto
+the stack. They are listed in the order in which they were pushed, the
last one being the current (topmost) patch."""
-directory = DirectoryHasRepository()
+directory = common.DirectoryHasRepositoryLib()
options = [make_option('-b', '--branch',
- help = 'use BRANCH instead of the default one'),
+ help = 'use BRANCH instead of the default branch'),
make_option('-c', '--count',
help = 'print the number of applied patches',
action = 'store_true')]
@@ -46,10 +42,13 @@ def func(parser, options, args):
if len(args) != 0:
parser.error('incorrect number of arguments')
- applied = crt_series.get_applied()
+ if options.branch:
+ s = directory.repository.get_stack(options.branch)
+ else:
+ s = directory.repository.current_stack
if options.count:
- out.stdout(len(applied))
+ out.stdout(len(s.patchorder.applied))
else:
- for p in applied:
- out.stdout(p)
+ for pn in s.patchorder.applied:
+ out.stdout(pn)
diff --git a/stgit/commands/unapplied.py b/stgit/commands/unapplied.py
index d5bb43e..7702207 100644
--- a/stgit/commands/unapplied.py
+++ b/stgit/commands/unapplied.py
@@ -16,13 +16,9 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
-import sys, os
-from optparse import OptionParser, make_option
-
-from stgit.commands.common import *
-from stgit.utils import *
+from optparse import make_option
from stgit.out import *
-from stgit import stack, git
+from stgit.commands import common
help = 'print the unapplied patches'
@@ -31,9 +27,9 @@ usage = """%prog [options]
List the patches from the series which are not pushed onto the stack.
They are listed in the reverse order in which they were popped."""
-directory = DirectoryHasRepository()
+directory = common.DirectoryHasRepositoryLib()
options = [make_option('-b', '--branch',
- help = 'use BRANCH instead of the default one'),
+ help = 'use BRANCH instead of the default branch'),
make_option('-c', '--count',
help = 'print the number of unapplied patches',
action = 'store_true')]
@@ -45,10 +41,13 @@ def func(parser, options, args):
if len(args) != 0:
parser.error('incorrect number of arguments')
- unapplied = crt_series.get_unapplied()
+ if options.branch:
+ s = directory.repository.get_stack(options.branch)
+ else:
+ s = directory.repository.current_stack
if options.count:
- out.stdout(len(unapplied))
+ out.stdout(len(s.patchorder.unapplied))
else:
- for p in unapplied:
- out.stdout(p)
+ for pn in s.patchorder.unapplied:
+ out.stdout(pn)
^ permalink raw reply related
* [StGit PATCH 07/10] Teach the new infrastructure about the index and worktree
From: Karl Hasselström @ 2007-11-25 20:51 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git, David Kågedal
In-Reply-To: <20071125203717.7823.70046.stgit@yoghurt>
And use the new powers to make "stg coalesce" able to handle arbitrary
patches, not just consecutive applied patches.
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
stgit/commands/clean.py | 4 +
stgit/commands/coalesce.py | 93 ++++++++++++++++---------
stgit/lib/git.py | 123 ++++++++++++++++++++++++++++++++++
stgit/lib/stack.py | 7 +-
stgit/lib/transaction.py | 161 ++++++++++++++++++++++++++++++++++++++------
5 files changed, 326 insertions(+), 62 deletions(-)
diff --git a/stgit/commands/clean.py b/stgit/commands/clean.py
index bbea253..e2d1678 100644
--- a/stgit/commands/clean.py
+++ b/stgit/commands/clean.py
@@ -44,7 +44,7 @@ def _clean(stack, clean_applied, clean_unapplied):
trans.unapplied = []
for pn in stack.patchorder.unapplied:
p = stack.patches.get(pn)
- if p.is_empty():
+ if p.commit.data.is_empty():
trans.patches[pn] = None
deleting(pn)
else:
@@ -54,7 +54,7 @@ def _clean(stack, clean_applied, clean_unapplied):
parent = stack.base
for pn in stack.patchorder.applied:
p = stack.patches.get(pn)
- if p.is_empty():
+ if p.commit.data.is_empty():
trans.patches[pn] = None
deleting(pn)
else:
diff --git a/stgit/commands/coalesce.py b/stgit/commands/coalesce.py
index c4c1cf8..2b121a9 100644
--- a/stgit/commands/coalesce.py
+++ b/stgit/commands/coalesce.py
@@ -35,50 +35,75 @@ options = [make_option('-n', '--name', help = 'name of coalesced patch'),
make_option('-m', '--message',
help = 'commit message of coalesced patch')]
-def _coalesce(stack, name, msg, patches):
- applied = stack.patchorder.applied
+def _coalesce_patches(trans, patches, msg):
+ cd = trans.patches[patches[0]].data
+ cd = git.Commitdata(tree = cd.tree, parents = cd.parents)
+ for pn in patches[1:]:
+ c = trans.patches[pn]
+ tree = trans.stack.repository.simple_merge(
+ base = c.data.parent.data.tree,
+ ours = cd.tree, theirs = c.data.tree)
+ if not tree:
+ return None
+ cd = cd.set_tree(tree)
+ if msg == None:
+ msg = '\n\n'.join('%s\n\n%s' % (pn.ljust(70, '-'),
+ trans.patches[pn].data.message)
+ for pn in patches)
+ msg = utils.edit_string(msg, '.stgit-coalesce.txt').strip()
+ cd = cd.set_message(msg)
- # Make sure the patches are consecutive.
- applied_ix = dict((applied[i], i) for i in xrange(len(applied)))
- ixes = list(sorted(applied_ix[p] for p in patches))
- i0, i1 = ixes[0], ixes[-1]
- if i1 - i0 + 1 != len(patches):
- raise common.CmdException('The patches must be consecutive')
+ return cd
- # Make a commit for the coalesced patch.
+def _coalesce(stack, iw, name, msg, patches):
+
+ # If a name was supplied on the command line, make sure it's OK.
def bad_name(pn):
return pn not in patches and stack.patches.exists(pn)
+ def get_name(cd):
+ return name or utils.make_patch_name(cd.message, bad_name)
if name and bad_name(name):
raise common.CmdException('Patch name "%s" already taken')
- ps = [stack.patches.get(pn) for pn in applied[i0:i1+1]]
- if msg == None:
- msg = '\n\n'.join('%s\n\n%s' % (p.name.ljust(70, '-'),
- p.commit.data.message)
- for p in ps)
- msg = utils.edit_string(msg, '.stgit-coalesce.txt').strip()
- if not name:
- name = utils.make_patch_name(msg, bad_name)
- cd = git.Commitdata(tree = ps[-1].commit.data.tree,
- parents = ps[0].commit.data.parents, message = msg)
- # Rewrite refs.
+ def make_coalesced_patch(trans, new_commit_data):
+ name = get_name(new_commit_data)
+ trans.patches[name] = stack.repository.commit(new_commit_data)
+ trans.unapplied.insert(0, name)
+
trans = transaction.StackTransaction(stack, 'stg coalesce')
- for pn in applied[i0:i1+1]:
- trans.patches[pn] = None
- parent = trans.patches[name] = stack.repository.commit(cd)
- trans.applied = applied[:i0]
- trans.applied.append(name)
- for pn in applied[i1+1:]:
- p = stack.patches.get(pn)
- parent = trans.patches[pn] = stack.repository.commit(
- p.commit.data.set_parent(parent))
- trans.applied.append(pn)
- trans.run()
+ push_new_patch = bool(set(patches) & set(trans.applied))
+ new_commit_data = _coalesce_patches(trans, patches, msg)
+ try:
+ if new_commit_data:
+ # We were able to construct the coalesced commit
+ # automatically. So just delete its constituent patches.
+ to_push = trans.delete_patches(lambda pn: pn in patches)
+ make_coalesced_patch(trans, new_commit_data)
+ else:
+ # Automatic construction failed. So push the patches
+ # consecutively, so that a second construction attempt is
+ # guaranteed to work.
+ to_push = trans.pop_patches(lambda pn: pn in patches)
+ for pn in patches:
+ trans.push_patch(pn, iw)
+ new_commit_data = _coalesce_patches(trans, patches, msg)
+ make_coalesced_patch(trans, new_commit_data)
+ assert not trans.delete_patches(lambda pn: pn in patches)
+
+ # Push the new patch if necessary, and any unrelated patches we've
+ # had to pop out of the way.
+ if push_new_patch:
+ trans.push_patch(get_name(new_commit_data), iw)
+ for pn in to_push:
+ trans.push_patch(pn, iw)
+ except transaction.TransactionHalted:
+ pass
+ trans.run(iw)
def func(parser, options, args):
stack = directory.repository.current_stack
- applied = set(stack.patchorder.applied)
- patches = set(common.parse_patches(args, list(stack.patchorder.applied)))
+ patches = common.parse_patches(args, list(stack.patchorder.applied))
if len(patches) < 2:
raise common.CmdException('Need at least two patches')
- _coalesce(stack, options.name, options.message, patches)
+ _coalesce(stack, stack.repository.default_iw(),
+ options.name, options.message, patches)
diff --git a/stgit/lib/git.py b/stgit/lib/git.py
index c4011f9..ab4a376 100644
--- a/stgit/lib/git.py
+++ b/stgit/lib/git.py
@@ -95,6 +95,8 @@ class Commitdata(Repr):
return type(self)(committer = committer, defaults = self)
def set_message(self, message):
return type(self)(message = message, defaults = self)
+ def is_empty(self):
+ return self.tree == self.parent.data.tree
def __str__(self):
if self.tree == None:
tree = None
@@ -218,6 +220,21 @@ class Repository(RunWithEnv):
).output_one_line())
except run.RunException:
raise RepositoryException('Cannot find git repository')
+ def default_index(self):
+ return Index(self, (os.environ.get('GIT_INDEX_FILE', None)
+ or os.path.join(self.__git_dir, 'index')))
+ def temp_index(self):
+ return Index(self, self.__git_dir)
+ def default_worktree(self):
+ path = os.environ.get('GIT_WORK_TREE', None)
+ if not path:
+ o = run.Run('git', 'rev-parse', '--show-cdup').output_lines()
+ o = o or ['.']
+ assert len(o) == 1
+ path = o[0]
+ return Worktree(path)
+ def default_iw(self):
+ return IndexAndWorktree(self.default_index(), self.default_worktree())
directory = property(lambda self: self.__git_dir)
refs = property(lambda self: self.__refs)
def cat_object(self, sha1):
@@ -258,3 +275,109 @@ class Repository(RunWithEnv):
raise DetachedHeadException()
def set_head(self, ref, msg):
self.run(['git', 'symbolic-ref', '-m', msg, 'HEAD', ref]).no_output()
+ def simple_merge(self, base, ours, theirs):
+ """Given three trees, tries to do an in-index merge in a temporary
+ index with a temporary index. Returns the result tree, or None if
+ the merge failed (due to conflicts)."""
+ assert isinstance(base, Tree)
+ assert isinstance(ours, Tree)
+ assert isinstance(theirs, Tree)
+
+ # Take care of the really trivial cases.
+ if base == ours:
+ return theirs
+ if base == theirs:
+ return ours
+ if ours == theirs:
+ return ours
+
+ index = self.temp_index()
+ try:
+ index.merge(base, ours, theirs)
+ try:
+ return index.write_tree()
+ except MergeException:
+ return None
+ finally:
+ index.delete()
+
+class MergeException(exception.StgException):
+ pass
+
+class Index(RunWithEnv):
+ def __init__(self, repository, filename):
+ self.__repository = repository
+ if os.path.isdir(filename):
+ # Create a temp index in the given directory.
+ self.__filename = os.path.join(
+ filename, 'index.temp-%d-%x' % (os.getpid(), id(self)))
+ self.delete()
+ else:
+ self.__filename = filename
+ env = property(lambda self: utils.add_dict(
+ self.__repository.env, { 'GIT_INDEX_FILE': self.__filename }))
+ def read_tree(self, tree):
+ self.run(['git', 'read-tree', tree.sha1]).no_output()
+ def write_tree(self):
+ try:
+ return self.__repository.get_tree(
+ self.run(['git', 'write-tree']).discard_stderr(
+ ).output_one_line())
+ except run.RunException:
+ raise MergeException('Conflicting merge')
+ def is_clean(self):
+ try:
+ self.run(['git', 'update-index', '--refresh']).discard_output()
+ except run.RunException:
+ return False
+ else:
+ return True
+ def merge(self, base, ours, theirs):
+ """In-index merge, no worktree involved."""
+ self.run(['git', 'read-tree', '-m', '-i', '--aggressive',
+ base.sha1, ours.sha1, theirs.sha1]).no_output()
+ def delete(self):
+ if os.path.isfile(self.__filename):
+ os.remove(self.__filename)
+
+class Worktree(object):
+ def __init__(self, directory):
+ self.__directory = directory
+ env = property(lambda self: { 'GIT_WORK_TREE': self.__directory })
+
+class CheckoutException(exception.StgException):
+ pass
+
+class IndexAndWorktree(RunWithEnv):
+ def __init__(self, index, worktree):
+ self.__index = index
+ self.__worktree = worktree
+ index = property(lambda self: self.__index)
+ env = property(lambda self: utils.add_dict(self.__index.env,
+ self.__worktree.env))
+ def checkout(self, old_tree, new_tree):
+ # TODO: Optionally do a 3-way instead of doing nothing when we
+ # have a problem. Or maybe we should stash changes in a patch?
+ assert isinstance(old_tree, Tree)
+ assert isinstance(new_tree, Tree)
+ try:
+ self.run(['git', 'read-tree', '-u', '-m',
+ '--exclude-per-directory=.gitignore',
+ old_tree.sha1, new_tree.sha1]
+ ).discard_output()
+ except run.RunException:
+ raise CheckoutException('Index/workdir dirty')
+ def merge(self, base, ours, theirs):
+ assert isinstance(base, Tree)
+ assert isinstance(ours, Tree)
+ assert isinstance(theirs, Tree)
+ try:
+ self.run(['git', 'merge-recursive', base.sha1, '--', ours.sha1,
+ theirs.sha1]).discard_output()
+ except run.RunException, e:
+ raise MergeException('Index/worktree dirty')
+ def changed_files(self):
+ return self.run(['git', 'diff-files', '--name-only']).output_lines()
+ def update_index(self, files):
+ self.run(['git', 'update-index', '--remove', '-z', '--stdin']
+ ).input_nulterm(files).discard_output()
diff --git a/stgit/lib/stack.py b/stgit/lib/stack.py
index 8fc8b08..b4512b1 100644
--- a/stgit/lib/stack.py
+++ b/stgit/lib/stack.py
@@ -64,9 +64,6 @@ class Patch(object):
self.__stack.repository.refs.delete(self.__ref)
def is_applied(self):
return self.name in self.__stack.patchorder.applied
- def is_empty(self):
- c = self.commit
- return c.data.tree == c.data.parent.data.tree
class PatchOrder(object):
"""Keeps track of patch order, and which patches are applied.
@@ -150,6 +147,10 @@ class Stack(object):
).commit.data.parent
else:
return self.head
+ def head_top_equal(self):
+ if not self.patchorder.applied:
+ return True
+ return self.head == self.patches.get(self.patchorder.applied[-1]).commit
class Repository(git.Repository):
def __init__(self, *args, **kwargs):
diff --git a/stgit/lib/transaction.py b/stgit/lib/transaction.py
index 991e64e..c9d355d 100644
--- a/stgit/lib/transaction.py
+++ b/stgit/lib/transaction.py
@@ -1,10 +1,14 @@
from stgit import exception
from stgit.out import *
+from stgit.lib import git
class TransactionException(exception.StgException):
pass
-def print_current_patch(old_applied, new_applied):
+class TransactionHalted(TransactionException):
+ pass
+
+def _print_current_patch(old_applied, new_applied):
def now_at(pn):
out.info('Now at patch "%s"' % pn)
if not old_applied and not new_applied:
@@ -18,22 +22,47 @@ def print_current_patch(old_applied, new_applied):
else:
now_at(new_applied[-1])
+class _TransPatchMap(dict):
+ def __init__(self, stack):
+ dict.__init__(self)
+ self.__stack = stack
+ def __getitem__(self, pn):
+ try:
+ return dict.__getitem__(self, pn)
+ except KeyError:
+ return self.__stack.patches.get(pn).commit
+
class StackTransaction(object):
def __init__(self, stack, msg):
self.__stack = stack
self.__msg = msg
- self.__patches = {}
+ self.__patches = _TransPatchMap(stack)
self.__applied = list(self.__stack.patchorder.applied)
self.__unapplied = list(self.__stack.patchorder.unapplied)
- def __set_patches(self, val):
- self.__patches = dict(val)
- patches = property(lambda self: self.__patches, __set_patches)
+ self.__error = None
+ self.__current_tree = self.__stack.head.data.tree
+ stack = property(lambda self: self.__stack)
+ patches = property(lambda self: self.__patches)
def __set_applied(self, val):
self.__applied = list(val)
applied = property(lambda self: self.__applied, __set_applied)
def __set_unapplied(self, val):
self.__unapplied = list(val)
unapplied = property(lambda self: self.__unapplied, __set_unapplied)
+ def __checkout(self, tree, iw):
+ if not self.__stack.head_top_equal():
+ out.error('HEAD and top are not the same.',
+ 'This can happen if you modify a branch with git.',
+ 'The "repair" command can fix this situation.')
+ self.__abort()
+ if self.__current_tree != tree:
+ assert iw != None
+ iw.checkout(self.__current_tree, tree)
+ self.__current_tree = tree
+ @staticmethod
+ def __abort():
+ raise TransactionException(
+ 'Command aborted (all changes rolled back)')
def __check_consistency(self):
remaining = set(self.__applied + self.__unapplied)
for pn, commit in self.__patches.iteritems():
@@ -41,29 +70,29 @@ class StackTransaction(object):
assert self.__stack.patches.exists(pn)
else:
assert pn in remaining
- def run(self):
- self.__check_consistency()
-
- # Get new head commit.
+ @property
+ def __head(self):
if self.__applied:
- top_patch = self.__applied[-1]
- try:
- new_head = self.__patches[top_patch]
- except KeyError:
- new_head = self.__stack.patches.get(top_patch).commit
+ return self.__patches[self.__applied[-1]]
else:
- new_head = self.__stack.base
+ return self.__stack.base
+ def run(self, iw = None):
+ self.__check_consistency()
+ new_head = self.__head
# Set branch head.
- if new_head == self.__stack.head:
- pass # same commit: OK
- elif new_head.data.tree == self.__stack.head.data.tree:
- pass # same tree: OK
- else:
- # We can't handle this case yet.
- raise TransactionException('Error: HEAD tree changed')
+ try:
+ self.__checkout(new_head.data.tree, iw)
+ except git.CheckoutException:
+ # We have to abort the transaction. The only state we need
+ # to restore is index+worktree.
+ self.__checkout(self.__stack.head.data.tree, iw)
+ self.__abort()
self.__stack.set_head(new_head, self.__msg)
+ if self.__error:
+ out.error(self.__error)
+
# Write patches.
for pn, commit in self.__patches.iteritems():
if self.__stack.patches.exists(pn):
@@ -74,6 +103,92 @@ class StackTransaction(object):
p.set_commit(commit, self.__msg)
else:
self.__stack.patches.new(pn, commit, self.__msg)
- print_current_patch(self.__stack.patchorder.applied, self.__applied)
+ _print_current_patch(self.__stack.patchorder.applied, self.__applied)
self.__stack.patchorder.applied = self.__applied
self.__stack.patchorder.unapplied = self.__unapplied
+
+ def __halt(self, msg):
+ self.__error = msg
+ raise TransactionHalted(msg)
+
+ @staticmethod
+ def __print_popped(popped):
+ if len(popped) == 0:
+ pass
+ elif len(popped) == 1:
+ out.info('Popped %s' % popped[0])
+ else:
+ out.info('Popped %s -- %s' % (popped[-1], popped[0]))
+
+ def pop_patches(self, p):
+ """Pop all patches pn for which p(pn) is true. Return the list of
+ other patches that had to be popped to accomplish this."""
+ popped = []
+ for i in xrange(len(self.applied)):
+ if p(self.applied[i]):
+ popped = self.applied[i:]
+ del self.applied[i:]
+ break
+ popped1 = [pn for pn in popped if not p(pn)]
+ popped2 = [pn for pn in popped if p(pn)]
+ self.unapplied = popped1 + popped2 + self.unapplied
+ self.__print_popped(popped)
+ return popped1
+
+ def delete_patches(self, p):
+ """Delete all patches pn for which p(pn) is true. Return the list of
+ other patches that had to be popped to accomplish this."""
+ popped = []
+ all_patches = self.applied + self.unapplied
+ for i in xrange(len(self.applied)):
+ if p(self.applied[i]):
+ popped = self.applied[i:]
+ del self.applied[i:]
+ break
+ popped = [pn for pn in popped if not p(pn)]
+ self.unapplied = popped + [pn for pn in self.unapplied if not p(pn)]
+ self.__print_popped(popped)
+ for pn in all_patches:
+ if p(pn):
+ s = ['', ' (empty)'][self.patches[pn].data.is_empty()]
+ self.patches[pn] = None
+ out.info('Deleted %s%s' % (pn, s))
+ return popped
+
+ def push_patch(self, pn, iw = None):
+ """Attempt to push the named patch. If this results in conflicts,
+ halts the transaction. If index+worktree are given, spill any
+ conflicts to them."""
+ i = self.unapplied.index(pn)
+ cd = self.patches[pn].data
+ s = ['', ' (empty)'][cd.is_empty()]
+ oldparent = cd.parent
+ cd = cd.set_parent(self.__head)
+ base = oldparent.data.tree
+ ours = cd.parent.data.tree
+ theirs = cd.tree
+ tree = self.__stack.repository.simple_merge(base, ours, theirs)
+ merge_conflict = False
+ if not tree:
+ if iw == None:
+ self.__halt('%s does not apply cleanly' % pn)
+ try:
+ self.__checkout(ours, iw)
+ except git.CheckoutException:
+ self.__halt('Index/worktree dirty')
+ try:
+ iw.merge(base, ours, theirs)
+ tree = iw.index.write_tree()
+ self.__current_tree = tree
+ s = ' (modified)'
+ except git.MergeException:
+ tree = ours
+ merge_conflict = True
+ s = ' (conflict)'
+ cd = cd.set_tree(tree)
+ self.patches[pn] = self.__stack.repository.commit(cd)
+ del self.unapplied[i]
+ self.applied.append(pn)
+ out.info('Pushed %s%s' % (pn, s))
+ if merge_conflict:
+ self.__halt('Merge conflict')
^ permalink raw reply related
* [StGit PATCH 08/10] Let "stg clean" use the new transaction primitives
From: Karl Hasselström @ 2007-11-25 20:51 UTC (permalink / raw)
To: Catalin Marinas; +Cc: git, David Kågedal
In-Reply-To: <20071125203717.7823.70046.stgit@yoghurt>
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
stgit/commands/clean.py | 33 +++++++--------------------------
1 files changed, 7 insertions(+), 26 deletions(-)
diff --git a/stgit/commands/clean.py b/stgit/commands/clean.py
index e2d1678..cfcc004 100644
--- a/stgit/commands/clean.py
+++ b/stgit/commands/clean.py
@@ -37,33 +37,14 @@ options = [make_option('-a', '--applied',
def _clean(stack, clean_applied, clean_unapplied):
- def deleting(pn):
- out.info('Deleting empty patch %s' % pn)
trans = transaction.StackTransaction(stack, 'clean')
- if clean_unapplied:
- trans.unapplied = []
- for pn in stack.patchorder.unapplied:
- p = stack.patches.get(pn)
- if p.commit.data.is_empty():
- trans.patches[pn] = None
- deleting(pn)
- else:
- trans.unapplied.append(pn)
- if clean_applied:
- trans.applied = []
- parent = stack.base
- for pn in stack.patchorder.applied:
- p = stack.patches.get(pn)
- if p.commit.data.is_empty():
- trans.patches[pn] = None
- deleting(pn)
- else:
- if parent != p.commit.data.parent:
- parent = trans.patches[pn] = stack.repository.commit(
- p.commit.data.set_parent(parent))
- else:
- parent = p.commit
- trans.applied.append(pn)
+ def del_patch(pn):
+ if pn in stack.patchorder.applied:
+ return clean_applied and trans.patches[pn].data.is_empty()
+ elif pn in stack.patchorder.unapplied:
+ return clean_unapplied and trans.patches[pn].data.is_empty()
+ for pn in trans.delete_patches(del_patch):
+ trans.push_patch(pn)
trans.run()
def func(parser, options, args):
^ permalink raw reply related
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