* Re: The git newbie experience
From: Shawn Pearce @ 2006-05-15 5:27 UTC (permalink / raw)
To: Tommi Virtanen; +Cc: Junio C Hamano, git
In-Reply-To: <44680C54.8040206@inoi.fi>
Tommi Virtanen <tv@inoi.fi> wrote:
> Junio C Hamano wrote:
[snip]
> > - Jack stashes away what he has been working on and cleans up
> > his mess.
> >
> > git diff >P.diff
> > git checkout HEAD A B C
> ...
> > - Jack then reapplies what he stashed away with "git apply P.diff"
> > and keeps working.
> >
> > Maybe "git stash" command that does "git diff --full-index" with
> > some frills, and "git unstash" command which does an equivalent
> > of "git am -3" would help this workflow (bare "git apply" does
> > not do the three-way merge like am does).
>
> Oh, I'd love to have a quick stash, that's what we actually ended up
> doing a lot. Although I'd rather see a real implementation use a branch
> and not just a diff file, but.. yes please.
>
> Although, "git stash" and "git unstash" are yet another command to add
> to the newbie set, and I just complained about the size of the set ;)
This is perhaps one area where SVN's user interface is actually nice.
SVN's equiv. of stash is making a copy of your working directory into
the repository; something that is rather simple to do for the user.
What about "git commit -b foo -a" to commit the current working
directory to branch 'foo'?
Then restoring is a pull of foo ("git pull . foo"), but that
intermediate commit is now part of the repository history. And "git
commit -a" doesn't automatically add extra/other files to the
repository and it probably should in the case of a "stash".
--
Shawn.
^ permalink raw reply
* Re: [PATCH] Fix compilation on newer NetBSD systems
From: Junio C Hamano @ 2006-05-15 5:31 UTC (permalink / raw)
To: Dennis Stosberg; +Cc: git
In-Reply-To: <20060511173531.G18d4553c@leonov.stosberg.net>
Dennis Stosberg <dennis@stosberg.net> writes:
> NetBSD >=2.0 has iconv() in libc. A libiconv is not required and
> does not exist.
I do not doubt that, but...
> + ifeq ($(shell test `uname -r | sed -e 's/^\([0-9]\).*/\1/'` -lt 2 && echo y),y)
> + NEEDS_LIBICONV = YesPlease
> + endif
This looks rather ugly. I do not know if NetBSD has 0.xx
versions, but perhaps something like this?
ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
^ permalink raw reply
* Re: The git newbie experience
From: Shawn Pearce @ 2006-05-15 5:31 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Tommi Virtanen, git
In-Reply-To: <7vy7x3x3ux.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano <junkio@cox.net> wrote:
> Tommi Virtanen <tv@inoi.fi> writes:
>
> > Oh, I'd love to have a quick stash, that's what we actually ended up
> > doing a lot. Although I'd rather see a real implementation use a branch
> > and not just a diff file, but.. yes please.
>
> I'd rather do that with a diff file that can be used to do a
> 3-way (see how rebase does it with --full-index diff with am -3).
> No point creating and forgetting to remove a throw away branch
> and getting more complaints.
How is a quick stash different from a topic branch? I don't see
any difference between the two. Your working directory was a topic
branch, just an unnamed topic branch. Why don't you name it and
deal with it once it is named?
I can see new users getting confused about what changes are in
their quick stash or accidentially losing their quick stash by
running it twice in a row.
Teaching new users to always work on a topic branch and committing
before pulling/merging should be the favored workflow.
--
Shawn.
^ permalink raw reply
* Re: [PATCH] send-email: allow sendmail binary to be used instead of SMTP
From: Junio C Hamano @ 2006-05-15 5:52 UTC (permalink / raw)
To: Eric Wong; +Cc: git
In-Reply-To: <1147660345772-git-send-email-normalperson@yhbt.net>
Eric Wong <normalperson@yhbt.net> writes:
> This should make local mailing possible for machines without
> a connection to an SMTP server.
Which is a good thing, but
> It'll default to using /usr/sbin/sendmail or /usr/lib/sendmail
> if no SMTP server is specified (the default). If it can't find
> either of those paths, it'll fall back to connecting to an SMTP
> server on localhost.
I do not know if it is OK to change the default to first prefer
local MDA executable and then "localhost". That is, ...
> @@ -179,8 +180,14 @@ if (!defined $initial_reply_to && $promp
> $initial_reply_to =~ s/(^\s+|\s+$)//g;
> }
>
> -if (!defined $smtp_server) {
> - $smtp_server = "localhost";
> +if (!$smtp_server) {
> + foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
> + if (-x $_) {
> + $smtp_server = $_;
> + last;
> + }
> + }
> + $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug*
> }
>
> if ($compose) {
Without this hunk, people who did not specify --smtp-server=host
could get away with having anything that listens to 25/tcp on
the localhost that is not either of the above two paths; now
they have to explicitly say --smtp-server=localhost to defeat
what this hunk does. I do not know if it is a big deal, though.
> + if ($smtp_server =~ m#^/#) {
I like this if(){}else{} here, but have a feeling that the
logging part should be placed outside it to be shared.
While we are at it, we might want to enhance $smtp_server parsing
to take host:port notation so that people can use message
submission port 587/tcp (RFC 4409) instead.
^ permalink raw reply
* how to display file history?
From: Brown, Len @ 2006-05-15 5:52 UTC (permalink / raw)
To: git
it is tiresome to access kernel.org/git tree display
to see the list of commits that changed a particular file.
(and for files on my local disk, this isn't available).
How do I print the list of commits that change a particular file
on my local disk?
thanks,
-Len
^ permalink raw reply
* Re: Tracking branch history
From: Shawn Pearce @ 2006-05-15 5:58 UTC (permalink / raw)
To: git
In-Reply-To: <20060515031511.GA27505@spearce.org>
Shawn Pearce <spearce@spearce.org> wrote:
> Log ref updates to logs/refs/<ref>
>
> If config parameter core.logAllRefUpdates is true or the log
> file already exists then append a line to ".git/logs/refs/<ref>"
> whenever git-update-ref <ref> is executed. Each log line contains
> the following information:
>
> oldsha1 <SP> newsha1 <SP> committer <LF>
>
> where committer is the current user, date, time and timezone in
> the standard GIT ident format. If the caller is unable to append
> to the log file then git-update-ref will fail without updating <ref>.
>
> An optional message may be included in the log line with the -m flag.
This is all well and good but its sort of useless without the diffcore
being able to lookup what SHA1 was valid on a given branch at a given
point in time. :-)
I'm thinking about extending the 'extended SHA1' syntax to accept
a date (or date expression) as a suffix:
HEAD@'2 hours ago'
HEAD@'2006-04-20'
HEAD@'2006-04-20 14:12'
etc... This would be merged into get_sha1 (sha1_name.c) so its
usable pretty much anywhere. Does this seem reasonable? If so
I'll work up a patch for it.
--
Shawn.
^ permalink raw reply
* Re: how to display file history?
From: Shawn Pearce @ 2006-05-15 6:00 UTC (permalink / raw)
To: Brown, Len; +Cc: git
In-Reply-To: <CFF307C98FEABE47A452B27C06B85BB670F4F8@hdsmsx411.amr.corp.intel.com>
"Brown, Len" <len.brown@intel.com> wrote:
> it is tiresome to access kernel.org/git tree display
> to see the list of commits that changed a particular file.
> (and for files on my local disk, this isn't available).
>
> How do I print the list of commits that change a particular file
> on my local disk?
I'm confused - why aren't these available on your local disk? Do you
not have a clone of the kernel repository local? If you don't have
a clone you aren't really going to be able to get a history.
But assuming you had one use whatchanged:
git whatchanged A
will show only the commits which affected file A, listing them in
reverse order (most recent to oldest).
--
Shawn.
^ permalink raw reply
* Re: [PATCH] send-email: quiet some warnings
From: Junio C Hamano @ 2006-05-15 6:04 UTC (permalink / raw)
To: Eric Wong; +Cc: git
In-Reply-To: <11476606883991-git-send-email-normalperson@yhbt.net>
Eric Wong <normalperson@yhbt.net> writes:
> @@ -507,8 +507,16 @@ sub unique_email_list(@) {
> my @emails;
>
> foreach my $entry (@_) {
> - my $clean = extract_valid_address($entry);
> - next if $seen{$clean}++;
> + if (my $clean = extract_valid_address($entry)) {
> + $seen{$clean} ||= 0;
> + next if $seen{$clean}++;
> + } else {
> + # it could still be a local email address without '@',
> + # which neither Email::Valid or our own small regex says
> + # is valid...
> + $seen{$entry} ||= 0;
> + next if $seen{$entry}++;
> + }
Wouldn't you want three kinds of return values from
sub extract_valid_address() if you want to do this? That is,
(1) address is valid and this is the cleaned up value;
(2) address is known to be bogus; do not use it.
(3) address might be local.
And (1) and (3) are probably the same thing in practice. In a
localsite setting, it is often convenient to be able to use
addr-spec that is local-part only, so something like the
attached change, with your error squelching for 'undef' return
in the second case, might be more appropriate.
-- >8 --
diff --git a/git-send-email.perl b/git-send-email.perl
index d8c4b1f..7a89e26 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -300,6 +300,9 @@ our ($message_id, $cc, %mail, $subject,
sub extract_valid_address {
my $address = shift;
+
+ return $address if ($address =~ /^(\w+)$/);
+
if ($have_email_valid) {
return Email::Valid->address($address);
} else {
^ permalink raw reply related
* RE: how to display file history?
From: Brown, Len @ 2006-05-15 6:13 UTC (permalink / raw)
To: spearce; +Cc: git
> git whatchanged A
thanks. I've used this on entire repos before, but
for some reason didn't think of this command name
when looking for individual file history.
Searching git(7) for "history" didn't take me here.
Searching for "log" would have, but I must have
terminated that search when git-log and git-shortlog
turned out to not be what I was looking for.
-Len
^ permalink raw reply
* Re: Tracking branch history
From: Junio C Hamano @ 2006-05-15 6:27 UTC (permalink / raw)
To: Shawn Pearce; +Cc: git
In-Reply-To: <20060515055830.GC28068@spearce.org>
Shawn Pearce <spearce@spearce.org> writes:
> This is all well and good but its sort of useless without the diffcore
> being able to lookup what SHA1 was valid on a given branch at a given
> point in time. :-)
>
> I'm thinking about extending the 'extended SHA1' syntax to accept
> a date (or date expression) as a suffix:
>
> HEAD@'2 hours ago'
> HEAD@'2006-04-20'
> HEAD@'2006-04-20 14:12'
>
> etc... This would be merged into get_sha1 (sha1_name.c) so its
> usable pretty much anywhere. Does this seem reasonable? If so
> I'll work up a patch for it.
HEAD?
Are you going to hook into symbolic-ref as well to track branch
switching?
Since there is no reverse pointer to tell which symbolic
reference is pointing at branch heads,and there are symbolic
references like refs/remotes/origin/HEAD that point at
refs/remotes/origin/master, detecting that such and such
symbolic refs are pointing at a branch that is advanced by a
call to update-ref and update the log for the symbolic refs that
point at it becomes rather expensive.
So probably you would need a separate log format that tracks
which concrete ref a symbolic ref was pointing at at any given
time and use that to keep track of them.
I personally doubt it is worth the trouble. I switch branches
between master, next and the topics all the time, and never is
interested in which branch I happened to be on 30 minutes ago.
The time-warp format would make sense for individual branches,
like refs/heads/master, though.
sha1_name.c and sha1_file.c were supposed to be real core, but
get_sha1() is looking more and more Porcelainish these days, and
I do not have much problem with being able to say "tip of this
branch, two hours ago".
I am not sure about the syntax though. We would want to be able
to say "start from the commit that was at the tip of 'master'
branch two days ago, grab its tree and look at arch/sparc64
directory", so things like
"master@2006-05-14 14:12"
"master@2006-05-14 14:12^{tree}"
"master@two days ago:arch/sparc64"
would need to be supported.
^ permalink raw reply
* Re: Tracking branch history
From: Shawn Pearce @ 2006-05-15 6:38 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vac9jx0nq.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano <junkio@cox.net> wrote:
> Shawn Pearce <spearce@spearce.org> writes:
>
> > This is all well and good but its sort of useless without the diffcore
> > being able to lookup what SHA1 was valid on a given branch at a given
> > point in time. :-)
> >
> > I'm thinking about extending the 'extended SHA1' syntax to accept
> > a date (or date expression) as a suffix:
> >
> > HEAD@'2 hours ago'
> > HEAD@'2006-04-20'
> > HEAD@'2006-04-20 14:12'
> >
> > etc... This would be merged into get_sha1 (sha1_name.c) so its
> > usable pretty much anywhere. Does this seem reasonable? If so
> > I'll work up a patch for it.
>
> HEAD?
>
> Are you going to hook into symbolic-ref as well to track branch
> switching?
I hadn't planned on it. I was going to resolve the symref HEAD
down to the real ref (e.g. refs/heads/sp/ref-log) and then do the
date range searching on the real branch. I didn't think it was
interesting to track what HEAD is. But I think it would be very
common for the user to use HEAD rather than their actual branch
ref names when forming an expression.
[snip]
> The time-warp format would make sense for individual branches,
> like refs/heads/master, though.
>
> sha1_name.c and sha1_file.c were supposed to be real core, but
> get_sha1() is looking more and more Porcelainish these days, and
> I do not have much problem with being able to say "tip of this
> branch, two hours ago".
>
> I am not sure about the syntax though. We would want to be able
> to say "start from the commit that was at the tip of 'master'
> branch two days ago, grab its tree and look at arch/sparc64
> directory", so things like
>
> "master@2006-05-14 14:12"
> "master@2006-05-14 14:12^{tree}"
> "master@two days ago:arch/sparc64"
>
> would need to be supported.
Yea, I realize that. I'm currently looking at get_sha1_1 and how
I can put the date resolution in before the ^, ~ and :. :-)
--
Shawn.
^ permalink raw reply
* Re: how to display file history?
From: Junio C Hamano @ 2006-05-15 6:42 UTC (permalink / raw)
To: Brown, Len; +Cc: git
In-Reply-To: <CFF307C98FEABE47A452B27C06B85BB670F4FD@hdsmsx411.amr.corp.intel.com>
"Brown, Len" <len.brown@intel.com> writes:
>
>> git whatchanged A
>
> thanks. I've used this on entire repos before, but
> for some reason didn't think of this command name
> when looking for individual file history.
Probably with recent enough git, one of
git log --stat -- A
git log -p -- A
git log -p --full-diff -- A
might be more pleasant, depending on what you are trying to look
for.
"A" can be a single file, more than one files, a directory,...
^ permalink raw reply
* Re: Simplify "git reset --hard"
From: Junio C Hamano @ 2006-05-15 6:55 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <Pine.LNX.4.64.0605141110150.3866@g5.osdl.org>
Linus Torvalds <torvalds@osdl.org> writes:
> Now that the one-way merge strategy does the right thing wrt files that do
> not exist in the result, just remove all the random crud we did in "git
> reset" to do this all by hand.
>
> Instead, just pass in "-u" to git-read-tree when we do a hard reset, and
> depend on git-read-tree to update the working tree appropriately.
Well, this is wrong. Local modifications remain after your
version of "git-reset --hard HEAD". which is not what we want
from a hard reset.
-- >8 --
diff --git a/git-reset.sh b/git-reset.sh
index 0ee3e3e..ecc111b 100755
--- a/git-reset.sh
+++ b/git-reset.sh
@@ -52,7 +52,7 @@ git-update-ref HEAD "$rev"
case "$reset_type" in
--hard )
- ;; # Nothing else to do
+ git-checkout-index -f -q -u -a ;;
--soft )
;; # Nothing else to do
--mixed )
^ permalink raw reply related
* Re: Simplify "git reset --hard"
From: Junio C Hamano @ 2006-05-15 7:08 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <7v1wuvwzdv.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano <junkio@cox.net> writes:
>> Instead, just pass in "-u" to git-read-tree when we do a hard reset, and
>> depend on git-read-tree to update the working tree appropriately.
>
> Well, this is wrong. Local modifications remain after your
> version of "git-reset --hard HEAD". which is not what we want
> from a hard reset.
... and attempting to paper it over in git-reset.sh is also
wrong. Keep your "--hard is noop" change in git-reset.sh and
replace it with this would be the right fix.
-- >8 --
diff --git a/read-tree.c b/read-tree.c
index 11157f4..c135f08 100644
--- a/read-tree.c
+++ b/read-tree.c
@@ -686,6 +686,7 @@ static int oneway_merge(struct cache_ent
if (!a)
return deleted_entry(old, NULL);
if (old && same(old, a)) {
+ old->ce_flags |= htons(CE_UPDATE);
return keep_entry(old);
}
return merged_entry(a, NULL);
^ permalink raw reply related
* Re: Simplify "git reset --hard"
From: Junio C Hamano @ 2006-05-15 7:46 UTC (permalink / raw)
To: Linus Torvalds; +Cc: git
In-Reply-To: <7vwtcnvk76.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano <junkio@cox.net> writes:
> Junio C Hamano <junkio@cox.net> writes:
>
>>> Instead, just pass in "-u" to git-read-tree when we do a hard reset, and
>>> depend on git-read-tree to update the working tree appropriately.
>>
>> Well, this is wrong. Local modifications remain after your
>> version of "git-reset --hard HEAD". which is not what we want
>> from a hard reset.
>
> ... and attempting to paper it over in git-reset.sh is also
> wrong. Keep your "--hard is noop" change in git-reset.sh and
> replace it with this would be the right fix.
-- >8 --
read-tree -u one-way merge fix to check out locally modified paths.
The "-u" flag means "update the working tree files", but to
other types of merges, it also implies "I want to keep my local
changes" -- because they prevent local changes from getting lost
by using verify_uptodate. The one-way merge is different from
other merges in that its purpose is opposite of doing something
else while keeping unrelated local changes. The point of
one-way merge is to nuke local changes. So while it feels
somewhat wrong that this actively loses local changes, it is the
right thing to do.
The earlier one marked old->ce_flags to be updated
unconditionally, but that would cause 18,000 paths to be updated
when you have only a few paths different from the HEAD you are
switching to, which is far worse than what we used to do in
git-reset by hand.
Signed-off-by: Junio C Hamano <junkio@cox.net>
---
* Third time lucky ;-)
diff --git a/read-tree.c b/read-tree.c
index 11157f4..d847c6f 100644
--- a/read-tree.c
+++ b/read-tree.c
@@ -686,6 +698,9 @@ static int oneway_merge(struct cache_ent
if (!a)
return deleted_entry(old, NULL);
if (old && same(old, a)) {
+ struct stat st;
+ if (lstat(old->name, &st) || ce_match_stat(old, &st, 1))
+ old->ce_flags |= htons(CE_UPDATE);
return keep_entry(old);
}
return merged_entry(a, NULL);
^ permalink raw reply related
* Re: The git newbie experience
From: Junio C Hamano @ 2006-05-15 8:39 UTC (permalink / raw)
To: Shawn Pearce, Tommi Virtanen; +Cc: git
In-Reply-To: <20060515053133.GB28068@spearce.org>
Shawn Pearce <spearce@spearce.org> writes:
>> I'd rather do that with a diff file that can be used to do a
>> 3-way (see how rebase does it with --full-index diff with am -3).
>> No point creating and forgetting to remove a throw away branch
>> and getting more complaints.
>
> How is a quick stash different from a topic branch?
The original version of my message in response to TV looked like
this.
- Jack is a beginning user of git and does not (want to) understand
the index (right now).
- Jack works on branch X, say his HEAD points to X1. He has an edited,
uncommitted files with the names A, B and C.
- Jack wants to pull new changes made by others to his branch.
But "git merge" invoked from "git pull" says he needs to stash
away the local changes to do the merge.
- Jack stashes away what he has been working on and cleans up
his mess.
git checkout -b stash ;# risks error when "stash" exists
git commit -a -m 'Stashing WIP'
git checkout master ;# assuming that was where he was
- Jack then pulls. There are merge conflicts in files D, E, ..., Z.
- Jack resolves the merge conflicts and is ready to commit the resulting
merge. Note files A, B and C do not have his unfinished work.
There is no "if Jack does this or that" problem; he says "git
commit -a" because that is the only "commit" command he knows
about.
- Jack then reapplies what he stashed away, and keeps working.
git pull . --no-commit stash
git branch -D stash
You have to teach the new user to (1) name something, only to
immediately discard it when he returns to what he was in the
middle of, (2) remember to clean up the temporary thing once he
is done lest he forgets to clean it up (and common names like
"stash", "tmp" will be reused by accident causing grief next
time he needs to do another stash), and (3) use of --no-commit
pull.
On the other hand, "git stash/unstash" workflow would be quite
simple:
$ git stash >my.precious.state
... do whatever you want to deviate to
$ git unstash <my.precious.state
Merge resolve might be needed while unstashing, but
we are talking about pulling somebody else's work in "do
whatever" part, so that is something the user knows how to
perform anyway.
A quick and dirty stash implementation would go like this:
Stash is easy.
#!/bin/sh
# git stash
git diff --binary HEAD
git reset --hard
Unstash is a bit involved.
#!/bin/sh
# git unstash
. git-sh-setup
O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
O_DIR=`cd "$GIT_DIR" && pwd`
stash="$O_DIR/.stash$$"
rm -fr "$stash.*"
trap 'rm -rf $stash.*' 0
cat >"$stash.patch"
git-apply -z --index-info <"$stash.patch" >"$stash.list"
GIT_INDEX_FILE="$stash.index" \
GIT_OBJECT_DIRECTORY="$O_OBJECT" \
(
mkdir -p "$stash.tmp" &&
git-update-index -z --index-info <"$stash.list" &&
git-write-tree >"$stash.base" &&
cd "$stash.tmp" &&
git-apply --binary --index <"$stash.patch" &&
git-write-tree >"$stash.his"
)
his_tree=$(cat "$stash.his")
orig_tree=$(cat "$stash.base")
rm -fr "$stash.*"
git-merge-resolve $orig_tree -- HEAD $his_tree
This is essentially the core of "am -3" logic; if you are going
to use this for real, you would probably want to see if the
patch applies cleanly before falling back on the three-way
merge, though.
^ permalink raw reply
* (Not) What's in git.git
From: Junio C Hamano @ 2006-05-15 8:46 UTC (permalink / raw)
To: git
Over the weekend everybody was busy feeding me patches, so I've
added much stuff in the "next", but without really looking deep
into each of them. It still passes the testsuite, but that
tells us more about the sparseness of the tests not the quality
of what are in the branch.
I'll review them in the coming week and have them graduate to
"master", perhaps slowly. Also what's currently in "maint" will
be tagged as 1.3.3.
I expect myself to be a bit slower for a couple of days at
least; I am still adjusting to the new development environment.
I haven't fully installed what I need nor configuired it
properly yet.
^ permalink raw reply
* align git diff a..b semantics with git log a..b
From: Salikh Zakirov @ 2006-05-15 8:41 UTC (permalink / raw)
To: git
Hi,
Currently, git-diff accepts
git-diff A..B
syntax, and the output seems to be equivalent of 'git-diff A B'.
IMHO, this conflicts with A..B semantics defined for git-log family of commands.
Consider following tree
O--X--Y--A1--A2 (a)
\
B1--B2 (b)
The command 'git log a..b' will show B1,B2, so I would find it
intuitive (and useful) to have 'git diff a..b' show B1+B2,
rather than -A2-A1+B1+B2.
So, I suggest to change semantics of 'git diff a..b'
to 'git diff `git-merge-base a b` b'.
I could contribute the documentation change if the idea is accepted and implemented.
Thanks a lot!
^ permalink raw reply
* Re: [PATCH] send-email: allow sendmail binary to be used instead of SMTP
From: Eric Wong @ 2006-05-15 9:27 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Martin Langhoff, Greg KH, Ryan Anderson
In-Reply-To: <7vpsifx2b7.fsf@assigned-by-dhcp.cox.net>
Junio C Hamano <junkio@cox.net> wrote:
> Eric Wong <normalperson@yhbt.net> writes:
>
> > This should make local mailing possible for machines without
> > a connection to an SMTP server.
>
> Which is a good thing, but
>
> > It'll default to using /usr/sbin/sendmail or /usr/lib/sendmail
> > if no SMTP server is specified (the default). If it can't find
> > either of those paths, it'll fall back to connecting to an SMTP
> > server on localhost.
>
> I do not know if it is OK to change the default to first prefer
> local MDA executable and then "localhost". That is, ...
>
> > @@ -179,8 +180,14 @@ if (!defined $initial_reply_to && $promp
> > $initial_reply_to =~ s/(^\s+|\s+$)//g;
> > }
> >
> > -if (!defined $smtp_server) {
> > - $smtp_server = "localhost";
> > +if (!$smtp_server) {
> > + foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
> > + if (-x $_) {
> > + $smtp_server = $_;
> > + last;
> > + }
> > + }
> > + $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug*
> > }
> >
> > if ($compose) {
>
> Without this hunk, people who did not specify --smtp-server=host
> could get away with having anything that listens to 25/tcp on
> the localhost that is not either of the above two paths; now
> they have to explicitly say --smtp-server=localhost to defeat
> what this hunk does. I do not know if it is a big deal, though.
I believe this is what Martin wanted. I think it's a good idea since
sendmail binaries tend to be more flexible, but I'm ok with it either
way.
Of course, Greg and Ryan were the original authors of this, so I'd
like their take on it, too.
> > + if ($smtp_server =~ m#^/#) {
>
> I like this if(){}else{} here, but have a feeling that the
> logging part should be placed outside it to be shared.
Cleaned that up a bit, patch coming. Also removed the Port: printout
completely, as it's rather redundant (see below).
> While we are at it, we might want to enhance $smtp_server parsing
> to take host:port notation so that people can use message
> submission port 587/tcp (RFC 4409) instead.
This already works, IO::Socket::INET (behind Net::SMTP) takes care of
it :)
--
Eric Wong
^ permalink raw reply
* [PATCH] send-email: allow sendmail binary to be used instead of SMTP
From: Eric Wong @ 2006-05-15 9:34 UTC (permalink / raw)
To: Junio C Hamano, Martin Langhoff, Ryan Anderson, Greg KH; +Cc: Eric Wong
In-Reply-To: <20060515092704.GB6855@localdomain>
This should make local mailing possible for machines without
a connection to an SMTP server.
It'll default to using /usr/sbin/sendmail or /usr/lib/sendmail
if no SMTP server is specified (the default). If it can't find
either of those paths, it'll fall back to connecting to an SMTP
server on localhost.
Signed-off-by: Eric Wong <normalperson@yhbt.net>
---
git-send-email.perl | 60 ++++++++++++++++++++++++++++++++++-----------------
1 files changed, 40 insertions(+), 20 deletions(-)
d4248a8ab7c883ab0f4dc080374bf60dc582f0f4
diff --git a/git-send-email.perl b/git-send-email.perl
index d8c4b1f..0540e93 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -40,7 +40,8 @@ # Variables we fill in automatically, or
my (@to,@cc,@initial_cc,$initial_reply_to,$initial_subject,@files,$from,$compose,$time);
# Behavior modification variables
-my ($chain_reply_to, $smtp_server, $quiet, $suppress_from, $no_signed_off_cc) = (1, "localhost", 0, 0, 0);
+my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc) = (1, 0, 0, 0);
+my $smtp_server;
# Example reply to:
#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
@@ -179,8 +180,14 @@ if (!defined $initial_reply_to && $promp
$initial_reply_to =~ s/(^\s+|\s+$)//g;
}
-if (!defined $smtp_server) {
- $smtp_server = "localhost";
+if (!$smtp_server) {
+ foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
+ if (-x $_) {
+ $smtp_server = $_;
+ last;
+ }
+ }
+ $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug*
}
if ($compose) {
@@ -358,26 +365,39 @@ X-Mailer: git-send-email $gitversion
";
$header .= "In-Reply-To: $reply_to\n" if $reply_to;
- $smtp ||= Net::SMTP->new( $smtp_server );
- $smtp->mail( $from ) or die $smtp->message;
- $smtp->to( @recipients ) or die $smtp->message;
- $smtp->data or die $smtp->message;
- $smtp->datasend("$header\n$message") or die $smtp->message;
- $smtp->dataend() or die $smtp->message;
- $smtp->ok or die "Failed to send $subject\n".$smtp->message;
-
+ if ($smtp_server =~ m#^/#) {
+ my $pid = open my $sm, '|-';
+ defined $pid or die $!;
+ if (!$pid) {
+ exec($smtp_server,'-i',@recipients) or die $!;
+ }
+ print $sm "$header\n$message";
+ close $sm or die $?;
+ } else {
+ $smtp ||= Net::SMTP->new( $smtp_server );
+ $smtp->mail( $from ) or die $smtp->message;
+ $smtp->to( @recipients ) or die $smtp->message;
+ $smtp->data or die $smtp->message;
+ $smtp->datasend("$header\n$message") or die $smtp->message;
+ $smtp->dataend() or die $smtp->message;
+ $smtp->ok or die "Failed to send $subject\n".$smtp->message;
+ }
if ($quiet) {
printf "Sent %s\n", $subject;
} else {
- print "OK. Log says:
-Date: $date
-Server: $smtp_server Port: 25
-From: $from
-Subject: $subject
-Cc: $cc
-To: $to
-
-Result: ", $smtp->code, ' ', ($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
+ print "OK. Log says:\nDate: $date\n";
+ if ($smtp) {
+ print "Server: $smtp_server\n";
+ } else {
+ print "Sendmail: $smtp_server\n";
+ }
+ print "From: $from\nSubject: $subject\nCc: $cc\nTo: $to\n\n";
+ if ($smtp) {
+ print "Result: ", $smtp->code, ' ',
+ ($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
+ } else {
+ print "Result: OK\n";
+ }
}
}
--
1.3.2.g7d11
^ permalink raw reply related
* [PATCH] send-email: quiet some warnings, reject invalid addresses
From: Eric Wong @ 2006-05-15 9:41 UTC (permalink / raw)
To: git, Junio C Hamano, Martin Langhoff, Ryan Anderson, Greg KH; +Cc: Eric Wong
In-Reply-To: <7vlkt3x1qz.fsf@assigned-by-dhcp.cox.net>
I'm not sure why we never actually rejected invalid addresses in
the first place. We just seemed to be using our email validity
checkers to kill duplicates.
Now we just drop invalid email addresses completely and warn
the user about it.
Since we support local sendmail, we'll also accept username-only
addresses.
Signed-off-by: Eric Wong <normalperson@yhbt.net>
---
git-send-email.perl | 15 ++++++++++++---
1 files changed, 12 insertions(+), 3 deletions(-)
f069e9a9cee726d82dfeee1d477152a0fe0651c1
diff --git a/git-send-email.perl b/git-send-email.perl
index 0540e93..312a4ea 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -307,6 +307,10 @@ our ($message_id, $cc, %mail, $subject,
sub extract_valid_address {
my $address = shift;
+
+ # check for a local address:
+ return $address if ($address =~ /^([\w\-]+)$/);
+
if ($have_email_valid) {
return Email::Valid->address($address);
} else {
@@ -498,9 +502,14 @@ sub unique_email_list(@) {
my @emails;
foreach my $entry (@_) {
- my $clean = extract_valid_address($entry);
- next if $seen{$clean}++;
- push @emails, $entry;
+ if (my $clean = extract_valid_address($entry)) {
+ $seen{$clean} ||= 0;
+ next if $seen{$clean}++;
+ push @emails, $entry;
+ } else {
+ print STDERR "W: unable to extract a valid address",
+ " from: $entry\n";
+ }
}
return @emails;
}
--
1.3.2.g7d11
^ permalink raw reply related
* Re: [PATCH] send-email: allow sendmail binary to be used instead of SMTP
From: Junio C Hamano @ 2006-05-15 9:47 UTC (permalink / raw)
To: Eric Wong; +Cc: git
In-Reply-To: <20060515092704.GB6855@localdomain>
Eric Wong <normalperson@yhbt.net> writes:
> I believe this is what Martin wanted. I think it's a good idea since
> sendmail binaries tend to be more flexible, but I'm ok with it either
> way.
I am not opposed to have an option to run a local submission
agent binary (I said I like that if(){}else{} there, didn't I?).
The ability to do so is a good thing. I am not however sure
about changing the default when no option is specified on the
command line.
>> > + if ($smtp_server =~ m#^/#) {
>>
>> I like this if(){}else{} here, but have a feeling that the
>> logging part should be placed outside it to be shared.
>
> Cleaned that up a bit, patch coming. Also removed the Port: printout
> completely, as it's rather redundant (see below).
>
>> While we are at it, we might want to enhance $smtp_server parsing
>> to take host:port notation so that people can use message
>> submission port 587/tcp (RFC 4409) instead.
>
> This already works, IO::Socket::INET (behind Net::SMTP) takes care of
> it :)
Thanks. Maybe the next option would be delivery to a file (or
even SMTP batch)? ;-)
^ permalink raw reply
* Re: Tracking branch history
From: Shawn Pearce @ 2006-05-15 9:53 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <20060515063849.GA28337@spearce.org>
Shawn Pearce <spearce@spearce.org> wrote:
> Junio C Hamano <junkio@cox.net> wrote:
> > Shawn Pearce <spearce@spearce.org> writes:
> >
> > > This is all well and good but its sort of useless without the diffcore
> > > being able to lookup what SHA1 was valid on a given branch at a given
> > > point in time. :-)
> > >
> > > I'm thinking about extending the 'extended SHA1' syntax to accept
> > > a date (or date expression) as a suffix:
> > >
> > > HEAD@'2 hours ago'
> > > HEAD@'2006-04-20'
> > > HEAD@'2006-04-20 14:12'
> > >
> > > etc... This would be merged into get_sha1 (sha1_name.c) so its
> > > usable pretty much anywhere. Does this seem reasonable? If so
> > > I'll work up a patch for it.
This is a preliminary patch for this syntax. I haven't handled the
absolute date parsing yet; I was hoping to use the same syntax
accepted by GIT_AUTHOR_DATE/GIT_COMMITTER_DATE but looking at the
code in date.c it wasn't going to be easily reused. I'll work
on it more tomorrow, right now I have to go do my day job to pay
the rent. :-)
Hmm... A quick look at date.c indicates I should be able to clean up
this parse_date_spec function quite a bit by using code from date.c.
I'll look at it more later.
-- >8 -
Support 'master@2 hours ago' syntax
Extended sha1 expressions may now include date specifications
which indicate a point in time within the local repository's
history. If the ref indicated to the left of '@' has a log in
$GIT_DIR/logs/<ref> then the value of the ref at the time indicated
by the specification is obtained from the ref's log.
---
Documentation/git-rev-parse.txt | 6 ++
refs.c | 52 ++++++++++++++
refs.h | 3 +
sha1_name.c | 145 ++++++++++++++++++++++++++++++++++-----
4 files changed, 189 insertions(+), 17 deletions(-)
1f16364fd8cadb6cdeb0b14a6f5439f02b578924
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index ab896fc..df308c3 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -124,6 +124,12 @@ syntax.
happen to have both heads/master and tags/master, you can
explicitly say 'heads/master' to tell git which one you mean.
+* A suffix '@' followed by a date specification such as 'yesterday'
+ (24 hours ago) or '1 month 2 weeks 3 days 1 hour 1 second ago'
+ to specify the value of the ref at a prior point in time.
+ This suffix may only be used immediately following a ref name
+ and the ref must have an existing log ($GIT_DIR/logs/<ref>).
+
* A suffix '{caret}' to a revision parameter means the first parent of
that commit object. '{caret}<n>' means the <n>th parent (i.e.
'rev{caret}'
diff --git a/refs.c b/refs.c
index a50ea8f..da009ac 100644
--- a/refs.c
+++ b/refs.c
@@ -440,3 +440,55 @@ int log_ref_update(const char *ref, cons
close(logfd);
return 0;
}
+
+int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1)
+{
+ const char *logfile, *logdata, *rec, *c;
+ char *tz_c;
+ int logfd, tz;
+ struct stat st;
+ unsigned long date;
+ unsigned char oldsha1[20];
+
+ logfile = git_path("logs/%s", ref);
+ logfd = open(logfile, O_RDONLY, 0);
+ if (logfd < 0)
+ die("Unable to read log %s: %s", logfile, strerror(errno));
+ fstat(logfd, &st);
+ if (!st.st_size)
+ die("Log %s is empty.", logfile);
+ logdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+ close(logfd);
+
+ rec = logdata + st.st_size;
+ while (logdata < rec) {
+ if (logdata < rec && *(rec-1) == '\n')
+ rec--;
+ while (logdata < rec && *(rec-1) != '\n')
+ rec--;
+ c = rec;
+ while (*c++ != '>')
+ /* nada */;
+ date = strtoul(c, NULL, 10);
+ if (date <= at_time) {
+ if (get_sha1_hex(rec, oldsha1))
+ die("Log %s is corrupt.", logfile);
+ if (get_sha1_hex(rec + 41, sha1))
+ die("Log %s is corrupt.", logfile);
+ munmap((void*)logdata, st.st_size);
+ return 0;
+ }
+ }
+
+ c = logdata;
+ while (*c++ != '>')
+ /* nada */;
+ date = strtoul(c, &tz_c, 10);
+ tz = strtoul(tz_c, NULL, 10);
+ if (get_sha1_hex(logdata, sha1))
+ die("Log %s is corrupt.", logfile);
+ munmap((void*)logdata, st.st_size);
+ fprintf(stderr, "warning: Log %s only goes back to %s.\n",
+ logfile, show_rfc2822_date(date, tz));
+ return 0;
+}
diff --git a/refs.h b/refs.h
index de3cb92..4831cdb 100644
--- a/refs.h
+++ b/refs.h
@@ -31,4 +31,7 @@ extern int check_ref_format(const char *
/** If logging is enabled logs the change made to the ref. **/
extern int log_ref_update(const char *ref, const unsigned char *currsha1, const unsigned char *newsha1, const char *msg);
+/** Reads log for the value of ref during at_time. **/
+extern int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1);
+
#endif /* REFS_H */
diff --git a/sha1_name.c b/sha1_name.c
index dc68355..5f33aea 100644
--- a/sha1_name.c
+++ b/sha1_name.c
@@ -4,6 +4,7 @@ #include "commit.h"
#include "tree.h"
#include "blob.h"
#include "tree-walk.h"
+#include "refs.h"
static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
{
@@ -234,6 +235,98 @@ static int ambiguous_path(const char *pa
return slash;
}
+static unsigned long parse_date_spec(const char *str, int len)
+{
+ long delta;
+ time_t now;
+
+ time(&now);
+ if (len == 9 && !strncasecmp("yesterday", str, 9))
+ return now - 24 * 60 * 60;
+ if (len > 4 && !strncasecmp(" ago", str + (len - 4), 4)) {
+ len -= 4;
+ while (len) {
+ if (len > 2 && !strncasecmp("a ", str, 2)) {
+ delta = 1;
+ len -= 2;
+ str += 2;
+ }
+ else if (len > 3 && !strncasecmp("an ", str, 3)) {
+ delta = 1;
+ len -= 2;
+ str += 2;
+ } else {
+ delta = 0;
+ while (len && isdigit(*str)) {
+ if (delta)
+ delta *= 10;
+ delta += *str++ - '0';
+ len--;
+ }
+ if (!delta)
+ return (time_t)-1;
+ while (len && isspace(*str)) {
+ str++;
+ len--;
+ }
+ }
+
+ if (len >= 5 && !strncasecmp("month", str, 5)) {
+ len -= 5;
+ str += 5;
+ now -= 30 * 24 * 60 * 60 * delta;
+ }
+ else if (len >= 4 && !strncasecmp("week", str, 4)) {
+ len -= 4;
+ str += 4;
+ now -= 7 * 24 * 60 * 60 * delta;
+ }
+ else if (len >= 3 && !strncasecmp("day", str, 3)) {
+ len -= 3;
+ str += 3;
+ now -= 24 * 60 * 60 * delta;
+ }
+ else if (len >= 4 && !strncasecmp("hour", str, 4)) {
+ len -= 4;
+ str += 4;
+ now -= 60 * 60 * delta;
+ }
+ else if (len >= 6 && !strncasecmp("minute", str, 6)) {
+ len -= 6;
+ str += 6;
+ now -= 60 * delta;
+ }
+ else if (len >= 3 && !strncasecmp("min", str, 3)) {
+ len -= 3;
+ str += 3;
+ now -= 60 * delta;
+ }
+ else if (len >= 6 && !strncasecmp("second", str, 6)) {
+ len -= 6;
+ str += 6;
+ now -= delta;
+ }
+ else if (len >= 3 && !strncasecmp("sec", str, 3)) {
+ len -= 3;
+ str += 3;
+ now -= delta;
+ }
+
+ if (len && *str == 's') {
+ len -= 1;
+ str += 1;
+ }
+
+ while (len && isspace(*str)) {
+ str++;
+ len--;
+ }
+ }
+ return now;
+ }
+ return (time_t)-1;
+}
+
static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
{
static const char *fmt[] = {
@@ -245,36 +338,54 @@ static int get_sha1_basic(const char *st
"refs/remotes/%.*s/HEAD",
NULL
};
- const char **p;
- const char *warning = "warning: refname '%.*s' is ambiguous.\n";
- char *pathname;
- int already_found = 0;
+ static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
+ const char **p, *pathname;
+ char *real_path = NULL;
+ int refs_found = 0, at_mark;
+ unsigned long at_time = (unsigned long)-1;
unsigned char *this_result;
unsigned char sha1_from_ref[20];
if (len == 40 && !get_sha1_hex(str, sha1))
return 0;
+ /* At a given period of time? "@2 hours ago" */
+ for (at_mark = 1; at_mark < len; at_mark++) {
+ if (str[at_mark] == '@') {
+ at_time = parse_date_spec(str + at_mark + 1, len - at_mark - 1);
+ if (at_time == (unsigned long)-1)
+ die("Invalid date spec after @ in '%.*s'", len, str);
+ len = at_mark;
+ }
+ }
+
/* Accept only unambiguous ref paths. */
if (ambiguous_path(str, len))
return -1;
for (p = fmt; *p; p++) {
- this_result = already_found ? sha1_from_ref : sha1;
- pathname = git_path(*p, len, str);
- if (!read_ref(pathname, this_result)) {
- if (warn_ambiguous_refs) {
- if (already_found)
- fprintf(stderr, warning, len, str);
- already_found++;
- }
- else
- return 0;
+ this_result = refs_found ? sha1_from_ref : sha1;
+ pathname = resolve_ref(git_path(*p, len, str), this_result, 1);
+ if (pathname) {
+ if (!refs_found++)
+ real_path = strdup(pathname);
+ if (!warn_ambiguous_refs)
+ break;
}
}
- if (already_found)
- return 0;
- return -1;
+
+ if (!refs_found)
+ return -1;
+
+ if (warn_ambiguous_refs && refs_found > 1)
+ fprintf(stderr, warning, len, str);
+
+ if (at_time != (unsigned long)-1) {
+ read_ref_at(real_path + strlen(git_path(".")) - 1, at_time, sha1);
+ }
+
+ free(real_path);
+ return 0;
}
static int get_sha1_1(const char *name, int len, unsigned char *sha1);
--
1.3.2.g7278
^ permalink raw reply related
* [PATCH 1/3] Don't die when there are no branches
From: Karl Hasselström @ 2006-05-15 9:53 UTC (permalink / raw)
To: Catalin Marinas; +Cc: Wartan Hachaturow, git
In-Reply-To: <20060510060040.GA3034@diana.vm.bytemark.co.uk>
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
stgit/commands/branch.py | 11 +++++++----
1 files changed, 7 insertions(+), 4 deletions(-)
c32f6b7bfd81bdbdb136ff72a4ad073e162ab97c
diff --git a/stgit/commands/branch.py b/stgit/commands/branch.py
index c7561a8..2218bbb 100644
--- a/stgit/commands/branch.py
+++ b/stgit/commands/branch.py
@@ -174,11 +174,14 @@ def func(parser, options, args):
branches = os.listdir(os.path.join(basedir.get(), 'refs', 'heads'))
branches.sort()
- max_len = max([len(i) for i in branches])
- print 'Available branches:'
- for i in branches:
- __print_branch(i, max_len)
+ if branches:
+ print 'Available branches:'
+ max_len = max([len(i) for i in branches])
+ for i in branches:
+ __print_branch(i, max_len)
+ else:
+ print 'No branches'
return
elif options.protect:
--
1.3.2.g639c
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle
^ permalink raw reply related
* [PATCH 2/3] Handle branch names with slashes
From: Karl Hasselström @ 2006-05-15 9:54 UTC (permalink / raw)
To: Catalin Marinas; +Cc: Wartan Hachaturow, git
In-Reply-To: <20060510060040.GA3034@diana.vm.bytemark.co.uk>
Teach stgit to handle branch names with slashes in them; that is,
branches living in a subdirectory of .git/refs/heads.
I had to change the patch@branch/top command-line syntax to
patch@branch#top, in order to get sane parsing. The /top variant is
still available for repositories that have no slashy branches; it is
disabled as soon as there exists at least one subdirectory of
refs/heads. Preferably, this compatibility hack can be killed some
time in the future.
Signed-off-by: Karl Hasselström <kha@treskal.com>
---
stgit/commands/branch.py | 5 ++
stgit/commands/common.py | 103 ++++++++++++++++++++++++++--------------------
stgit/commands/diff.py | 12 +++--
stgit/commands/files.py | 4 +-
stgit/commands/id.py | 2 -
stgit/commands/mail.py | 8 ++--
stgit/git.py | 42 +++++++++----------
stgit/stack.py | 21 ++++++---
stgit/utils.py | 88 +++++++++++++++++++++++++++++++++++++--
9 files changed, 193 insertions(+), 92 deletions(-)
466723a40f8d40b84a23fe720447e1938e22c0a5
diff --git a/stgit/commands/branch.py b/stgit/commands/branch.py
index 2218bbb..d348409 100644
--- a/stgit/commands/branch.py
+++ b/stgit/commands/branch.py
@@ -172,7 +172,10 @@ def func(parser, options, args):
if len(args) != 0:
parser.error('incorrect number of arguments')
- branches = os.listdir(os.path.join(basedir.get(), 'refs', 'heads'))
+ branches = []
+ basepath = os.path.join(basedir.get(), 'refs', 'heads')
+ for path, files, dirs in walk_tree(basepath):
+ branches += [os.path.join(path, f) for f in files]
branches.sort()
if branches:
diff --git a/stgit/commands/common.py b/stgit/commands/common.py
index c6ca514..9439976 100644
--- a/stgit/commands/common.py
+++ b/stgit/commands/common.py
@@ -18,7 +18,7 @@ along with this program; if not, write t
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
-import sys, os, re
+import sys, os, os.path, re
from optparse import OptionParser, make_option
from stgit.utils import *
@@ -34,54 +34,69 @@ class CmdException(Exception):
# Utility functions
+class RevParseException(Exception):
+ """Revision spec parse error."""
+ pass
+
+def parse_rev(rev):
+ """Parse a revision specification into its
+ patchname@branchname#patch_id parts. If no branch name has a slash
+ in it, also accept / instead of #."""
+ files, dirs = list_files_and_dirs(os.path.join(basedir.get(),
+ 'refs', 'heads'))
+ if len(dirs) != 0:
+ # We have branch names with / in them.
+ branch_chars = '[^@#]'
+ patch_id_mark = '#'
+ else:
+ # No / in branch names.
+ branch_chars = '[^@#/]'
+ patch_id_mark = '[/#]'
+ patch_re = r'(?P<patch>[^@/#]+)'
+ branch_re = r'@(?P<branch>%s+)' % branch_chars
+ patch_id_re = r'%s(?P<patch_id>[a-z.]*)' % patch_id_mark
+
+ # Try #patch_id.
+ m = re.match(r'^%s$' % patch_id_re, rev)
+ if m:
+ return None, None, m.group('patch_id')
+
+ # Try patch[@branch][#patch_id].
+ m = re.match(r'^%s(%s)?(%s)?$' % (patch_re, branch_re, patch_id_re), rev)
+ if m:
+ return m.group('patch'), m.group('branch'), m.group('patch_id')
+
+ # No, we can't parse that.
+ raise RevParseException
+
def git_id(rev):
"""Return the GIT id
"""
if not rev:
return None
-
- rev_list = rev.split('/')
- if len(rev_list) == 2:
- patch_id = rev_list[1]
- if not patch_id:
- patch_id = 'top'
- elif len(rev_list) == 1:
- patch_id = 'top'
- else:
- patch_id = None
-
- patch_branch = rev_list[0].split('@')
- if len(patch_branch) == 1:
- series = crt_series
- elif len(patch_branch) == 2:
- series = stack.Series(patch_branch[1])
- else:
- raise CmdException, 'Unknown id: %s' % rev
-
- patch_name = patch_branch[0]
- if not patch_name:
- patch_name = series.get_current()
- if not patch_name:
- raise CmdException, 'No patches applied'
-
- # patch
- if patch_name in series.get_applied() \
- or patch_name in series.get_unapplied():
- if patch_id == 'top':
- return series.get_patch(patch_name).get_top()
- elif patch_id == 'bottom':
- return series.get_patch(patch_name).get_bottom()
- # Note we can return None here.
- elif patch_id == 'top.old':
- return series.get_patch(patch_name).get_old_top()
- elif patch_id == 'bottom.old':
- return series.get_patch(patch_name).get_old_bottom()
-
- # base
- if patch_name == 'base' and len(rev_list) == 1:
- return read_string(series.get_base_file())
-
- # anything else failed
+ try:
+ patch, branch, patch_id = parse_rev(rev)
+ if branch == None:
+ series = crt_series
+ else:
+ series = stack.Series(branch)
+ if patch == None:
+ patch = series.get_current()
+ if not patch:
+ raise CmdException, 'No patches applied'
+ if patch in series.get_applied() or patch in series.get_unapplied():
+ if patch_id in ['top', '', None]:
+ return series.get_patch(patch).get_top()
+ elif patch_id == 'bottom':
+ return series.get_patch(patch).get_bottom()
+ elif patch_id == 'top.old':
+ return series.get_patch(patch).get_old_top()
+ elif patch_id == 'bottom.old':
+ return series.get_patch(patch).get_old_bottom()
+ if patch == 'base' and patch_id == None:
+ return read_string(series.get_base_file())
+ except RevParseException:
+ pass
return git.rev_parse(rev + '^{commit}')
def check_local_changes():
diff --git a/stgit/commands/diff.py b/stgit/commands/diff.py
index 7dc6c5d..c3ab7d8 100644
--- a/stgit/commands/diff.py
+++ b/stgit/commands/diff.py
@@ -33,12 +33,12 @@ or a tree-ish object and another tree-is
be given to restrict the diff output. The tree-ish object can be a
standard git commit, tag or tree. In addition to these, the command
also supports 'base', representing the bottom of the current stack,
-and '[patch]/[bottom | top]' for the patch boundaries (defaulting to
+and '[patch]#[bottom | top]' for the patch boundaries (defaulting to
the current one):
-rev = '([patch]/[bottom | top]) | <tree-ish> | base'
+rev = '([patch]#[bottom | top]) | <tree-ish> | base'
-If neither bottom or top are given but a '/' is present, the command
+If neither bottom or top are given but a '#' is present, the command
shows the specified patch (defaulting to the current one)."""
options = [make_option('-r', metavar = 'rev1[:[rev2]]', dest = 'revs',
@@ -55,10 +55,10 @@ def func(parser, options, args):
rev_list = options.revs.split(':')
rev_list_len = len(rev_list)
if rev_list_len == 1:
- if rev_list[0][-1] == '/':
+ if rev_list[0][-1] in ['/', '#']:
# the whole patch
- rev1 = rev_list[0] + 'bottom'
- rev2 = rev_list[0] + 'top'
+ rev1 = rev_list[0][:-1] + '#bottom'
+ rev2 = rev_list[0][:-1] + '#top'
else:
rev1 = rev_list[0]
rev2 = None
diff --git a/stgit/commands/files.py b/stgit/commands/files.py
index 0694d83..1d5126e 100644
--- a/stgit/commands/files.py
+++ b/stgit/commands/files.py
@@ -53,8 +53,8 @@ def func(parser, options, args):
else:
parser.error('incorrect number of arguments')
- rev1 = git_id('%s/bottom' % patch)
- rev2 = git_id('%s/top' % patch)
+ rev1 = git_id('%s#bottom' % patch)
+ rev2 = git_id('%s#top' % patch)
if options.stat:
print git.diffstat(rev1 = rev1, rev2 = rev2)
diff --git a/stgit/commands/id.py b/stgit/commands/id.py
index 1cf6ea6..5202aa4 100644
--- a/stgit/commands/id.py
+++ b/stgit/commands/id.py
@@ -28,7 +28,7 @@ usage = """%prog [options] [id]
Print the hash value of a GIT id (defaulting to HEAD). In addition to
the standard GIT id's like heads and tags, this command also accepts
-'base[@<branch>]' and '[<patch>[@<branch>]][/(bottom | top)]'. If no
+'base[@<branch>]' and '[<patch>[@<branch>]][#(bottom | top)]'. If no
'top' or 'bottom' are passed and <patch> is a valid patch name, 'top'
will be used by default."""
diff --git a/stgit/commands/mail.py b/stgit/commands/mail.py
index 5e01ea1..937efa3 100644
--- a/stgit/commands/mail.py
+++ b/stgit/commands/mail.py
@@ -324,10 +324,10 @@ def __build_message(tmpl, patch, patch_n
'shortdescr': short_descr,
'longdescr': long_descr,
'endofheaders': headers_end,
- 'diff': git.diff(rev1 = git_id('%s/bottom' % patch),
- rev2 = git_id('%s/top' % patch)),
- 'diffstat': git.diffstat(rev1 = git_id('%s/bottom'%patch),
- rev2 = git_id('%s/top' % patch)),
+ 'diff': git.diff(rev1 = git_id('%s#bottom' % patch),
+ rev2 = git_id('%s#top' % patch)),
+ 'diffstat': git.diffstat(rev1 = git_id('%s#bottom'%patch),
+ rev2 = git_id('%s#top' % patch)),
'date': email.Utils.formatdate(localtime = True),
'version': version_str,
'patchnr': patch_nr_str,
diff --git a/stgit/git.py b/stgit/git.py
index 2884f36..716609c 100644
--- a/stgit/git.py
+++ b/stgit/git.py
@@ -225,7 +225,8 @@ def get_head():
def get_head_file():
"""Returns the name of the file pointed to by the HEAD link
"""
- return os.path.basename(_output_one_line('git-symbolic-ref HEAD'))
+ return strip_prefix('refs/heads/',
+ _output_one_line('git-symbolic-ref HEAD'))
def set_head_file(ref):
"""Resets HEAD to point to a new ref
@@ -233,7 +234,8 @@ def set_head_file(ref):
# head cache flushing is needed since we might have a different value
# in the new head
__clear_head_cache()
- if __run('git-symbolic-ref HEAD', [ref]) != 0:
+ if __run('git-symbolic-ref HEAD',
+ [os.path.join('refs', 'heads', ref)]) != 0:
raise GitException, 'Could not set head to "%s"' % ref
def __set_head(val):
@@ -272,6 +274,7 @@ def rev_parse(git_id):
def branch_exists(branch):
"""Existence check for the named branch
"""
+ branch = os.path.join('refs', 'heads', branch)
for line in _output_lines('git-rev-parse --symbolic --all 2>&1'):
if line.strip() == branch:
return True
@@ -282,12 +285,11 @@ def branch_exists(branch):
def create_branch(new_branch, tree_id = None):
"""Create a new branch in the git repository
"""
- new_head = os.path.join('refs', 'heads', new_branch)
- if branch_exists(new_head):
+ if branch_exists(new_branch):
raise GitException, 'Branch "%s" already exists' % new_branch
current_head = get_head()
- set_head_file(new_head)
+ set_head_file(new_branch)
__set_head(current_head)
# a checkout isn't needed if new branch points to the current head
@@ -297,22 +299,22 @@ def create_branch(new_branch, tree_id =
if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')):
os.remove(os.path.join(basedir.get(), 'MERGE_HEAD'))
-def switch_branch(name):
+def switch_branch(new_branch):
"""Switch to a git branch
"""
global __head
- new_head = os.path.join('refs', 'heads', name)
- if not branch_exists(new_head):
- raise GitException, 'Branch "%s" does not exist' % name
+ if not branch_exists(new_branch):
+ raise GitException, 'Branch "%s" does not exist' % new_branch
- tree_id = rev_parse(new_head + '^{commit}')
+ tree_id = rev_parse(os.path.join('refs', 'heads', new_branch)
+ + '^{commit}')
if tree_id != get_head():
refresh_index()
if __run('git-read-tree -u -m', [get_head(), tree_id]) != 0:
raise GitException, 'git-read-tree failed (local changes maybe?)'
__head = tree_id
- set_head_file(new_head)
+ set_head_file(new_branch)
if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')):
os.remove(os.path.join(basedir.get(), 'MERGE_HEAD'))
@@ -320,25 +322,23 @@ def switch_branch(name):
def delete_branch(name):
"""Delete a git branch
"""
- branch_head = os.path.join('refs', 'heads', name)
- if not branch_exists(branch_head):
+ if not branch_exists(name):
raise GitException, 'Branch "%s" does not exist' % name
- os.remove(os.path.join(basedir.get(), branch_head))
+ remove_file_and_dirs(os.path.join(basedir.get(), 'refs', 'heads'),
+ name)
def rename_branch(from_name, to_name):
"""Rename a git branch
"""
- from_head = os.path.join('refs', 'heads', from_name)
- if not branch_exists(from_head):
+ if not branch_exists(from_name):
raise GitException, 'Branch "%s" does not exist' % from_name
- to_head = os.path.join('refs', 'heads', to_name)
- if branch_exists(to_head):
+ if branch_exists(to_name):
raise GitException, 'Branch "%s" already exists' % to_name
if get_head_file() == from_name:
- set_head_file(to_head)
- os.rename(os.path.join(basedir.get(), from_head), \
- os.path.join(basedir.get(), to_head))
+ set_head_file(to_name)
+ rename(os.path.join(basedir.get(), 'refs', 'heads'),
+ from_name, to_name)
def add(names):
"""Add the files or recursively add the directory contents
diff --git a/stgit/stack.py b/stgit/stack.py
index f83161b..49b50e7 100644
--- a/stgit/stack.py
+++ b/stgit/stack.py
@@ -443,8 +443,7 @@ class Series:
os.makedirs(self.__patch_dir)
- if not os.path.isdir(bases_dir):
- os.makedirs(bases_dir)
+ create_dirs(bases_dir)
create_empty_file(self.__applied_file)
create_empty_file(self.__unapplied_file)
@@ -502,11 +501,14 @@ class Series:
git.rename_branch(self.__name, to_name)
if os.path.isdir(self.__series_dir):
- os.rename(self.__series_dir, to_stack.__series_dir)
+ rename(os.path.join(self.__base_dir, 'patches'),
+ self.__name, to_stack.__name)
if os.path.exists(self.__base_file):
- os.rename(self.__base_file, to_stack.__base_file)
+ rename(os.path.join(self.__base_dir, 'refs', 'bases'),
+ self.__name, to_stack.__name)
if os.path.exists(self.__refs_dir):
- os.rename(self.__refs_dir, to_stack.__refs_dir)
+ rename(os.path.join(self.__base_dir, 'refs', 'patches'),
+ self.__name, to_stack.__name)
self.__init__(to_name)
@@ -560,16 +562,19 @@ class Series:
else:
print 'Patch directory %s is not empty.' % self.__name
if not os.listdir(self.__series_dir):
- os.rmdir(self.__series_dir)
+ remove_dirs(os.path.join(self.__base_dir, 'patches'),
+ self.__name)
else:
print 'Series directory %s is not empty.' % self.__name
if not os.listdir(self.__refs_dir):
- os.rmdir(self.__refs_dir)
+ remove_dirs(os.path.join(self.__base_dir, 'refs', 'patches'),
+ self.__name)
else:
print 'Refs directory %s is not empty.' % self.__refs_dir
if os.path.exists(self.__base_file):
- os.remove(self.__base_file)
+ remove_file_and_dirs(
+ os.path.join(self.__base_dir, 'refs', 'bases'), self.__name)
def refresh_patch(self, files = None, message = None, edit = False,
show_patch = False,
diff --git a/stgit/utils.py b/stgit/utils.py
index 5749b3b..68b8f58 100644
--- a/stgit/utils.py
+++ b/stgit/utils.py
@@ -1,6 +1,8 @@
"""Common utility functions
"""
+import errno, os, os.path
+
__copyright__ = """
Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
@@ -18,6 +20,12 @@ along with this program; if not, write t
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
+def mkdir_file(filename, mode):
+ """Opens filename with the given mode, creating the directory it's
+ in if it doesn't already exist."""
+ create_dirs(os.path.dirname(filename))
+ return file(filename, mode)
+
def read_string(filename, multiline = False):
"""Reads the first line from a file
"""
@@ -32,7 +40,7 @@ def read_string(filename, multiline = Fa
def write_string(filename, line, multiline = False):
"""Writes 'line' to file and truncates it
"""
- f = file(filename, 'w+')
+ f = mkdir_file(filename, 'w+')
if multiline:
f.write(line)
else:
@@ -42,7 +50,7 @@ def write_string(filename, line, multili
def append_strings(filename, lines):
"""Appends 'lines' sequence to file
"""
- f = file(filename, 'a+')
+ f = mkdir_file(filename, 'a+')
for line in lines:
print >> f, line
f.close()
@@ -50,14 +58,14 @@ def append_strings(filename, lines):
def append_string(filename, line):
"""Appends 'line' to file
"""
- f = file(filename, 'a+')
+ f = mkdir_file(filename, 'a+')
print >> f, line
f.close()
def insert_string(filename, line):
"""Inserts 'line' at the beginning of the file
"""
- f = file(filename, 'r+')
+ f = mkdir_file(filename, 'r+')
lines = f.readlines()
f.seek(0); f.truncate()
print >> f, line
@@ -67,4 +75,74 @@ def insert_string(filename, line):
def create_empty_file(name):
"""Creates an empty file
"""
- file(name, 'w+').close()
+ mkdir_file(name, 'w+').close()
+
+def list_files_and_dirs(path):
+ """Return the sets of filenames and directory names in a
+ directory."""
+ files, dirs = [], []
+ for fd in os.listdir(path):
+ full_fd = os.path.join(path, fd)
+ if os.path.isfile(full_fd):
+ files.append(fd)
+ elif os.path.isdir(full_fd):
+ dirs.append(fd)
+ return files, dirs
+
+def walk_tree(basedir):
+ """Starting in the given directory, iterate through all its
+ subdirectories. For each subdirectory, yield the name of the
+ subdirectory (relative to the base directory), the list of
+ filenames in the subdirectory, and the list of directory names in
+ the subdirectory."""
+ subdirs = ['']
+ while subdirs:
+ subdir = subdirs.pop()
+ files, dirs = list_files_and_dirs(os.path.join(basedir, subdir))
+ for d in dirs:
+ subdirs.append(os.path.join(subdir, d))
+ yield subdir, files, dirs
+
+def strip_prefix(prefix, string):
+ """Return string, without the prefix. Blow up if string doesn't
+ start with prefix."""
+ assert string.startswith(prefix)
+ return string[len(prefix):]
+
+def remove_dirs(basedir, dirs):
+ """Starting at join(basedir, dirs), remove the directory if empty,
+ and try the same with its parent, until we find a nonempty
+ directory or reach basedir."""
+ path = dirs
+ while path:
+ try:
+ os.rmdir(os.path.join(basedir, path))
+ except OSError:
+ return # can't remove nonempty directory
+ path = os.path.dirname(path)
+
+def remove_file_and_dirs(basedir, file):
+ """Remove join(basedir, file), and then remove the directory it
+ was in if empty, and try the same with its parent, until we find a
+ nonempty directory or reach basedir."""
+ os.remove(os.path.join(basedir, file))
+ remove_dirs(basedir, os.path.dirname(file))
+
+def create_dirs(directory):
+ """Create the given directory, if the path doesn't already exist."""
+ if directory:
+ create_dirs(os.path.dirname(directory))
+ try:
+ os.mkdir(directory)
+ except OSError, e:
+ if e.errno != errno.EEXIST:
+ raise e
+
+def rename(basedir, file1, file2):
+ """Rename join(basedir, file1) to join(basedir, file2), not
+ leaving any empty directories behind and creating any directories
+ necessary."""
+ full_file2 = os.path.join(basedir, file2)
+ create_dirs(os.path.dirname(full_file2))
+ os.rename(os.path.join(basedir, file1), full_file2)
+ remove_dirs(basedir, os.path.dirname(file1))
--
1.3.2.g639c
--
Karl Hasselström, kha@treskal.com
www.treskal.com/kalle
^ 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