* git 'new' alias
From: Thiago Farina @ 2011-09-01 19:30 UTC (permalink / raw)
To: Git Mailing List
Hi,
Could we change the 'new' alias in the wiki page,
from:
new = !sh -c 'git log $1@{1}..$1@{0} "$@"'
to:
new = !git log $1@{1}..$1@{0} "$@"
?
The former doesn't get auto-completion, while the second yes.
https://git.wiki.kernel.org/index.php/Aliases#What.27s_new.3F
^ permalink raw reply
* Re: Dropping '+' from fetch = +refs/heads/*:refs/remotes/origin/*?
From: Michael J Gruber @ 2011-09-01 19:20 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vliu8w25g.fsf@alter.siamese.dyndns.org>
Junio C Hamano venit, vidit, dixit 01.09.2011 20:25:
> Suggested reading:
>
> http://git-blame.blogspot.com/2011/08/how-to-inject-malicious-commit-to-git.html
>
> I am wondering if we are better off applying something along the lines of
> this patch, so that with the default configuration, users can notice if
> their upstream unexpectedly rewound their branches.
>
> It would produce
>
> [remote]
> url = git://.../git.git/
> fetch = refs/heads/*:refs/remotes/origin/*
>
> upon cloning from my repository, and your "git fetch" will fail because
> the pu (proposed updates) branch is constantly unwinding, but that can be
> easily fixed with
>
>
> [remote]
> url = git://.../git.git/
> fetch = refs/heads/*:refs/remotes/origin/*
> fetch = +refs/heads/pu:refs/remotes/origin/pu
>
> as the explicit refspec trumps the wildcard one.
>
> builtin/remote.c | 6 +++---
> 1 files changed, 3 insertions(+), 3 deletions(-)
My first thought was "Oh no", even though I saw it coming since I read
your blog entry. But I realized that it was probably due to the usual
inertia when habits have to change.
Thinking more about it, we try to encourage a workflow where locally
history may be rewritten a lot, and distribution points fast-forward
only. We have defaults and settings to discourage (pushes to checked out
branches and) non-ff pushes, for example. So I think the above change is
pretty much in line with that reasoning.
The kernel.org problems gave git some pretty good PR even without that
change, but with it we have an even stronger stop put in. On the other
hand, adding "+" to the config for "pu" (and peff...) isn't that much of
an issue, though we might also want "fetch -f" as an override - I guess
we have that already.
Plus fetch with fsck, yes.
To
"I don't need backups, I let others clone."
add
"I don't need intrusion detection, I let others fetch."
;)
Michael
^ permalink raw reply
* Re: Dropping '+' from fetch = +refs/heads/*:refs/remotes/origin/*?
From: Shawn Pearce @ 2011-09-01 19:14 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vhb4ww1id.fsf@alter.siamese.dyndns.org>
On Thu, Sep 1, 2011 at 11:39, Junio C Hamano <gitster@pobox.com> wrote:
> Junio C Hamano <gitster@pobox.com> writes:
>
>> Suggested reading:
>>
>> http://git-blame.blogspot.com/2011/08/how-to-inject-malicious-commit-to-git.html
>>
>> I am wondering if we are better off applying something along the lines of
>> this patch, so that with the default configuration, users can notice if
>> their upstream unexpectedly rewound their branches.
>>
>> It would produce
>>
>> [remote]
>> url = git://.../git.git/
>> fetch = refs/heads/*:refs/remotes/origin/*
>>
>> upon cloning from my repository, and your "git fetch" will fail because
>> the pu (proposed updates) branch is constantly unwinding, but that can be
>> easily fixed with
>>
>>
>> [remote]
>> url = git://.../git.git/
>> fetch = refs/heads/*:refs/remotes/origin/*
>> fetch = +refs/heads/pu:refs/remotes/origin/pu
>>
>> as the explicit refspec trumps the wildcard one.
>
> It appears that we have a glitch somewhere in the implementation. We
> should make the explicit refspec trump the wildcarded ones.
This is a great idea. :-)
--
Shawn.
^ permalink raw reply
* Re: [PATCH] test-lib: save test counts across invocations
From: Jeff King @ 2011-09-01 18:45 UTC (permalink / raw)
To: Alex Vandiver; +Cc: Thomas Rast, git, Junio C Hamano
In-Reply-To: <1314902334.5371.17.camel@umgah.localdomain>
On Thu, Sep 01, 2011 at 02:38:54PM -0400, Alex Vandiver wrote:
> On Thu, 2011-09-01 at 12:38 -0400, Jeff King wrote:
> > Hmm. What happens when we're wrong? Does our eye-candy just print
> > something non-sensical like "13/12", or does prove actually care that we
> > run the right number of tests?
>
> prove very much does care -- having a mismatch between the number of
> tests planned and the number of tests run is an error in the testfile,
> and is reported as such in big red text. This is because stating how
> many tests you plan to run gives prove a way (in addition to the exit
> status) to know if the test stopped prematurely, so all mismatches
> between plan and actual test counts are reported as testfile failures.
Thanks. I suspected something like that, but was too lazy to look. :)
Given that our methods for automatically determining the number of tests
are so flaky, and that prove will treat it so seriously, it doesn't seem
worth pursuing to me.
We already handle the premature abort case by trapping exit from the
shell before the script calls test_done. So I don't think that is a
feature of prove that we particularly care about.
> As far as I know prove doesn't have a way to print the estimated time
> remaining, though using the contents of the .prove file (if you ran
> prove --state=save) to guess it wouldn't be all that hard of a change.
That would be a neat feature. In practice, I know about how many tests
there are total (~7500), and how long it takes to run on my system (~60
seconds), so I can do the math myself. Still, a little more eye candy
couldn't hurt. ;)
If I underestand the code correctly, we could even write our own custom
"formatter" for git and use it via "prove --formatter".
-Peff
^ permalink raw reply
* Re: Dropping '+' from fetch = +refs/heads/*:refs/remotes/origin/*?
From: Junio C Hamano @ 2011-09-01 18:39 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7vliu8w25g.fsf@alter.siamese.dyndns.org>
Junio C Hamano <gitster@pobox.com> writes:
> Suggested reading:
>
> http://git-blame.blogspot.com/2011/08/how-to-inject-malicious-commit-to-git.html
>
> I am wondering if we are better off applying something along the lines of
> this patch, so that with the default configuration, users can notice if
> their upstream unexpectedly rewound their branches.
>
> It would produce
>
> [remote]
> url = git://.../git.git/
> fetch = refs/heads/*:refs/remotes/origin/*
>
> upon cloning from my repository, and your "git fetch" will fail because
> the pu (proposed updates) branch is constantly unwinding, but that can be
> easily fixed with
>
>
> [remote]
> url = git://.../git.git/
> fetch = refs/heads/*:refs/remotes/origin/*
> fetch = +refs/heads/pu:refs/remotes/origin/pu
>
> as the explicit refspec trumps the wildcard one.
It appears that we have a glitch somewhere in the implementation. We
should make the explicit refspec trump the wildcarded ones.
^ permalink raw reply
* Re: [PATCH] test-lib: save test counts across invocations
From: Alex Vandiver @ 2011-09-01 18:38 UTC (permalink / raw)
To: Jeff King; +Cc: Thomas Rast, git, Junio C Hamano
In-Reply-To: <20110901163846.GD15018@sigill.intra.peff.net>
On Thu, 2011-09-01 at 12:38 -0400, Jeff King wrote:
> Hmm. What happens when we're wrong? Does our eye-candy just print
> something non-sensical like "13/12", or does prove actually care that we
> run the right number of tests?
prove very much does care -- having a mismatch between the number of
tests planned and the number of tests run is an error in the testfile,
and is reported as such in big red text. This is because stating how
many tests you plan to run gives prove a way (in addition to the exit
status) to know if the test stopped prematurely, so all mismatches
between plan and actual test counts are reported as testfile failures.
As far as I know prove doesn't have a way to print the estimated time
remaining, though using the contents of the .prove file (if you ran
prove --state=save) to guess it wouldn't be all that hard of a change.
- Alex
^ permalink raw reply
* Re: git-checkout silently throws away the dirty status of index without a warning?
From: Tzu-Jung Lee @ 2011-09-01 18:28 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <7v1uw0xk57.fsf@alter.siamese.dyndns.org>
On Fri, Sep 2, 2011 at 1:11 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Tzu-Jung Lee <roylee17@gmail.com> writes:
>
>> Is this an intended behavior?
>
> Yes, I think you are talking about the case where "the current index does
> not match the current HEAD, but it does match the tree we are switching
> to" case. In that case we take the contents of switched-to branch.
>
> It is the last case in the table in this old design document:
>
> http://thread.gmane.org/gmane.comp.version-control.git/4641
>
Cool! It's really valuable resource about the internals. Do all the
entries of the matrix still hold true today?
Maybe we can have an updated version put in the manual or other
official documentation.
> bug ignore the terminology (stage#). Read only the body of the table, with
> the understanding that the three entries in each row talk about the state
> for the same path in the index entry, the tree entry in the current HEAD,
> and the tree entry in the switched-to branch. Also the table does not talk
> about the checking performed on the working tree file, but assume that we
> do not overwrite it when the resulting entry in the index does not match
> what you have there.
>
> The reason we allow branch switching in this case, instead of failing, is
> so that you can be in a state where you applied the same change (relative
> to the current branch to the branch you are switching to) lying around
> already in your workspace and safely switch to the new branch without
> losing any work (after all, the content matches).
I understand it's a safe behavior in the sense that all the 'content'
are preserved. But the 'work' may still lost in another sense.
Consider the extended scenario. I was in the middle of doing... kind
of 'in-commit-bisection' (btw, how would you call this?).
Finding out which are the guilty changes in a giant patch that has
large number of changes including binary updates.
(good) (good) (bad)
index HEAD switchd-to
A1 A1 A2
B2 B1 B2
C2 C1 C2
D2 D1 D2
E1 E1 E2
...
I accidentally switch to the switched-to branch without committing the
intermediate status.
The 'work' was gone, and I had no idea how to bring it back.
> By the way, the first six lines of your original message that describes
> "saving and applying" is not correct.
^ permalink raw reply
* Dropping '+' from fetch = +refs/heads/*:refs/remotes/origin/*?
From: Junio C Hamano @ 2011-09-01 18:25 UTC (permalink / raw)
To: git
Suggested reading:
http://git-blame.blogspot.com/2011/08/how-to-inject-malicious-commit-to-git.html
I am wondering if we are better off applying something along the lines of
this patch, so that with the default configuration, users can notice if
their upstream unexpectedly rewound their branches.
It would produce
[remote]
url = git://.../git.git/
fetch = refs/heads/*:refs/remotes/origin/*
upon cloning from my repository, and your "git fetch" will fail because
the pu (proposed updates) branch is constantly unwinding, but that can be
easily fixed with
[remote]
url = git://.../git.git/
fetch = refs/heads/*:refs/remotes/origin/*
fetch = +refs/heads/pu:refs/remotes/origin/pu
as the explicit refspec trumps the wildcard one.
builtin/remote.c | 6 +++---
1 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/builtin/remote.c b/builtin/remote.c
index f2a9c26..081fbbf 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -116,11 +116,11 @@ static int add_branch(const char *key, const char *branchname,
const char *remotename, int mirror, struct strbuf *tmp)
{
strbuf_reset(tmp);
- strbuf_addch(tmp, '+');
- if (mirror)
+ if (mirror) {
+ strbuf_addch(tmp, '+');
strbuf_addf(tmp, "refs/%s:refs/%s",
branchname, branchname);
- else
+ } else
strbuf_addf(tmp, "refs/heads/%s:refs/remotes/%s/%s",
branchname, remotename, branchname);
return git_config_set_multivar(key, tmp->buf, "^$", 0);
^ permalink raw reply related
* Re: [PATCH] for-each-ref: add split message parts to %(contents:*).
From: Jeff King @ 2011-09-01 18:19 UTC (permalink / raw)
To: Junio C Hamano; +Cc: Michał Górny, git, Michael J Gruber
In-Reply-To: <7vwrdsw5ci.fsf@alter.siamese.dyndns.org>
On Thu, Sep 01, 2011 at 10:16:29AM -0700, Junio C Hamano wrote:
> Jeff King <peff@peff.net> writes:
>
> > We could either leave %(subject) with its historical behavior, or fix it
> > to handle multi-line subjects. Although it's technically a regression to
> > change it, I tend to think it is simply a bug, as it doesn't match what
> > the rest of git (like "git log --format=%s") does.
>
> I think %(subject) should be updated to match %(contents:subject) as a
> bugfix, so %(body) should be adjusted to prevent "%(subject)%(body)" from
> duplicating lines.
OK. That makes it much easier to implement, too, I think. :)
> Side note. We probably would want to borrow from pretty-formats to allow
> us to say %(subject)%(+body) or something...
I have been meaning to take a closer look at Will Palmer's patches for
making the pretty-formats look more like the for-each-ref formats (which
I think would be the first step to unifying the features). But somehow
months have slipped by, and I haven't yet.
But I think unifying the formats and the code is better than trying to
port features across.
-Peff
^ permalink raw reply
* Funnies with "git fetch"
From: Junio C Hamano @ 2011-09-01 17:53 UTC (permalink / raw)
To: git
I just did this in an empty directory.
$ git init src
$ cd src
$ echo hello >greetings ; git add . ; git commit -m greetings
$ S=$(git rev-parse :greetings | sed -e 's|^..|&/|')
$ X=$(echo bye | git hash-object -w --stdin | sed -e 's|^..|&/|')
$ mv -f .git/objects/$X .git/objects/$S
The tip commit _thinks_ it has "greetings" that contains "hello", but
somebody replaced it with a corrupt "bye" that does not match self
integrity.
$ git fsck
error: sha1 mismatch ce013625030ba8dba906f756967f9e9ca394464a
error: ce013625030ba8dba906f756967f9e9ca394464a: object corrupt or missing
missing blob ce013625030ba8dba906f756967f9e9ca394464a
The "hello" blob is ce0136, and the tree contained in HEAD expects "hello"
in that loose object file, but notices the contents do not match the
filename.
So far, so good. Let's see what others see when they interact with this
repository.
cd ../
git init dst
cd dst
git config receive.fsckobjects true
git remote add origin ../src
git config branch.master.remote origin
git config branch.master.merge refs/heads/master
git fetch
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From ../src
* [new branch] master -> origin/master
Oops? If we run "fsck" at this point, we would notice the breakage:
$ git fsck
notice: HEAD points to an unborn branch (master)
broken link from tree 1c93b84c9756b083e5751db1f9ffa7f80ac667e2
to blob ce013625030ba8dba906f756967f9e9ca394464a
missing blob ce013625030ba8dba906f756967f9e9ca394464a
dangling blob b023018cabc396e7692c70bbf5784a93d3f738ab
Here, b02301 is the true identity of the "bye" blob the src repository
crafted and tried to fool us into believing it is "hello". We can see
that the object transfer gave three objects, and because we only propagate
the contents and have the receiving end compute the object names from the
data, we received b02301 but not ce0136.
$ ls .git/objects/??/?*
.git/objects/1c/93b84c9756b083e5751db1f9ffa7f80ac667e2
.git/objects/61/5d8c76daef6744635c87fb312a76a5ec7462ea
.git/objects/b0/23018cabc396e7692c70bbf5784a93d3f738ab
As a side note, if we did "git pull" instead of "git fetch", we would have
also noticed the breakage, like so:
$ git pull
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From ../src
* [new branch] master -> origin/master
error: unable to find ce013625030ba8dba906f756967f9e9ca394464a
error: unable to read sha1 file of greetings (ce013625030ba...)
But the straight "fetch" did not notice anything fishy going on. Shouldn't
we have? Even though we may be reasonably safe, unpack-objects should be
able to do better, especially under receive.fsckobjects option.
Also as a side note, if we set
$ git config fetch.unpacklimit 1
before we run this "git fetch", we end up storing a single pack, whose
contents are the same three objects above (as expected), and we do not get
any indication of an error from the command.
I think the breakages are:
- The sending side does not give any indication that it _wanted_ to send
ce0136 but couldn't, and ended up sending another object;
- The pack data sent over the wire was self consistent (no breakage here)
and sent three well-formed objects, but it was inconsistent with
respect to what history was being transferred (breakage is here);
- The receiving end did not notice the inconsistency.
The first one is of the lower priority, as the client side should be able
to notice an upstream with corruption in any case. Perhaps after asking
for objects between "have" and "want", "git fetch" should verify that it
can fully walk the subhistory that was supposed to be transferred down to
the blob level?
^ permalink raw reply
* Re: git-checkout silently throws away the dirty status of index without a warning?
From: Tzu-Jung Lee @ 2011-09-01 17:50 UTC (permalink / raw)
To: Jeff King; +Cc: git
In-Reply-To: <20110901165638.GE15018@sigill.intra.peff.net>
On Fri, Sep 2, 2011 at 12:56 AM, Jeff King <peff@peff.net> wrote:
> On Thu, Sep 01, 2011 at 11:47:59PM +0800, Tzu-Jung Lee wrote:
>
>> Correct me if I'm wrong:
>>
>> git-checkout saves the changes to index and working-tree, and
>> tries to apply them to the destined commit.
>> If the changes are applicable, then git-checkout the destined
>> commit and apply the changes.
>> Otherwise, git-checkout fails with warnings and leaves the current
>> status untouched.
>
> Not exactly. "git checkout <branch>" will switch your HEAD to <branch>,
> and then try to make your index and working tree match the contents of
> <branch>, with two exceptions:
>
> 1. If you have local changes in a file, but the contents of the file
> in <branch> do not differ from what's in the current HEAD, then the
> file will be left alone (i.e., your local changes will be
> preserved).
>
> 2. If you have local changes in a file, and the contents of the file
> in <branch> differ both from what's in your working tree and from
> what's in your current HEAD, git will print an error and refuse to
> overwrite your changes (though you can ask it to merge them with
> "git checkout -m").
>
> So it is not about "do these changes apply", but rather that we will
> give up any time file-level merging is required (unless "-m" is
> specified).
>
Ah!!! I think it was the "file-level merging" that surprised me so much.
I used to think it's an atomic "commit-level merging" -- cleanly apply
all the changes or touch nothing at all.
Having using git for years, I never notice this difference and neither
did it cause any trouble to me.
Until the corner case came to me today...
Thanks for the quick and precise explanation.
> The other form, "git checkout <branch> [--] <file>", is not about
> switching branches at all, but about putting content from <branch> into
> the current index and working tree, overwriting what's there.
>
>> If the above correct. Please help me clarify if the following corner
>> case an intended or unexpected behavior.
>> [...]
>> $ git checkout -b br1
>> $ git reset HEAD^
>> Unstaged changes after reset:
>> M aaa.txt
>> M bbb.txt
>
> So you have changes in two commits...
>
>> $ git checkout HEAD aaa.txt
>
> And here you explicitly overwrite the changes in aaa.txt.
>
>> $ git status --short
>> M bbb.txt
>
> ...leaving only the changes in bbb.txt.
>
>> $ git add bbb.txt
>> $ git status
>>
>> # On branch br1
>> # Changes to be committed:
>> # (use "git reset HEAD <file>..." to unstage)
>> #
>> # modified: bbb.txt
>> #
>
> OK, now it's staged.
>
>> $ git checkout master
>> Switched to branch 'master'
>>
>> git silently switch to master without warning against the index are
>> "RESTORE/RESET" to clean.
>
> Yes, because the changes in your index were identical to what was in the
> destination branch. So we didn't drop any changes; they're still in the
> index and in the working tree. It's simply that when compared to your
> new HEAD, they are uninteresting.
>
>> $ git checkout br1
>> $ git status
>> # On branch br1
>> nothing to commit (working directory clean)
>
> And now when we switch to br1, you have no changes against master in
> your working tree or index, so there is no dirty state to block
> switching branches.
>
> I think git is working as intended here. I agree it is a somewhat
> surprising corner case, but only because your changes happened to
> exactly match the difference between the two branches you are switching
> between. But it makes sense when you think about what "dirty state"
> means: it is differences between HEAD and your index and working tree.
> So we usually think of creating or removing dirty state by changing the
> working tree. But you could equally well do it by changing the HEAD
> without changing the working tree, which is what you did here.
>
> -Peff
>
^ permalink raw reply
* Re: obstack fails to compile on OS X 10.7
From: Ramsay Jones @ 2011-09-01 17:30 UTC (permalink / raw)
To: Fredrik Kuivinen; +Cc: David Aguilar, Brian Gernhardt, Git List
In-Reply-To: <20110828200846.GA10754@fredrik-Q430-Q530>
Fredrik Kuivinen wrote:
> On Sat, Aug 27, 2011 at 03:14:43AM -0700, David Aguilar wrote:
>> On Sat, Aug 27, 2011 at 02:21:40AM -0400, Brian Gernhardt wrote:
[snip]
>> I suspect that more exotic platforms may have problems
>> with obstack.h as well. This probably needs some testing
>> on SunOS, AIX, IRIX, etc.
Just FYI, my cygwin and mingw builds failed in the same way as OS X ...
> Something like this (tested on Linux and SunOS 5.10):
>
> -- 8< --
>
> Subject: [PATCH RFC] obstack: Fix portability issues
>
> i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1, SunOS 5.10, and possibly
> others do not have exit.h and exitfail.h. Remove the use of these in
> obstack.c.
>
> The __block variable was renamed to block to avoid a gcc error:
>
> compat/obstack.h:190: error: __block attribute can be specified on variables only
>
> Initial-patch-by: David Aguilar <davvid@gmail.com>
> Reported-by: Brian Gernhardt <brian@gernhardtsoftware.com>
> Signed-off-by: Fredrik Kuivinen <frekui@gmail.com>
> ---
... and this fixes the build(s) just fine[1]. Thanks!
ATB,
Ramsay Jones
[1] Well the build is fine on cygwin, but the mingw build fails for
an unrelated reason; compat/obstack.c compiles just fine.
^ permalink raw reply
* Re: [PATCH] for-each-ref: add split message parts to %(contents:*).
From: Junio C Hamano @ 2011-09-01 17:16 UTC (permalink / raw)
To: Jeff King; +Cc: Michał Górny, git, Michael J Gruber
In-Reply-To: <20110901162222.GC15018@sigill.intra.peff.net>
Jeff King <peff@peff.net> writes:
> We could either leave %(subject) with its historical behavior, or fix it
> to handle multi-line subjects. Although it's technically a regression to
> change it, I tend to think it is simply a bug, as it doesn't match what
> the rest of git (like "git log --format=%s") does.
I think %(subject) should be updated to match %(contents:subject) as a
bugfix, so %(body) should be adjusted to prevent "%(subject)%(body)" from
duplicating lines.
Thanks.
Side note. We probably would want to borrow from pretty-formats to allow
us to say %(subject)%(+body) or something...
^ permalink raw reply
* Re: Cannot rewrite branch(es) with a dirty working directory
From: Matthieu Moy @ 2011-09-01 17:14 UTC (permalink / raw)
To: James Blackburn; +Cc: git
In-Reply-To: <CACyv8dfL-G0Px0aFe3VFNHP-1xRUDLsFxgUu3amPGzO7qKp7jQ@mail.gmail.com>
James Blackburn <jamesblackburn@gmail.com> writes:
> Is there a particular reason why filter-branch thinks the tree is
> dirty,
No idea. It comes after a "git reset --hard", so it's supposed to be
clean.
> and status magically fixes this?
The index is considered dirty if the stat information (timestamp) is
different. "git status" updates the stat-cache to consider unmodified
content as up-to-date. So, it's normal that a "git status" repairs the
dirty index.
--
Matthieu Moy
http://www-verimag.imag.fr/~moy/
^ permalink raw reply
* Re: git-checkout silently throws away the dirty status of index without a warning?
From: Junio C Hamano @ 2011-09-01 17:11 UTC (permalink / raw)
To: Tzu-Jung Lee; +Cc: git
In-Reply-To: <CAEvN+1h+mY+f3dzK7LFOwkqokOZSS-LosCzBqtYGbyjz=Dg7Zw@mail.gmail.com>
Tzu-Jung Lee <roylee17@gmail.com> writes:
> Is this an intended behavior?
Yes, I think you are talking about the case where "the current index does
not match the current HEAD, but it does match the tree we are switching
to" case. In that case we take the contents of switched-to branch.
It is the last case in the table in this old design document:
http://thread.gmane.org/gmane.comp.version-control.git/4641
bug ignore the terminology (stage#). Read only the body of the table, with
the understanding that the three entries in each row talk about the state
for the same path in the index entry, the tree entry in the current HEAD,
and the tree entry in the switched-to branch. Also the table does not talk
about the checking performed on the working tree file, but assume that we
do not overwrite it when the resulting entry in the index does not match
what you have there.
The reason we allow branch switching in this case, instead of failing, is
so that you can be in a state where you applied the same change (relative
to the current branch to the branch you are switching to) lying around
already in your workspace and safely switch to the new branch without
losing any work (after all, the content matches).
By the way, the first six lines of your original message that describes
"saving and applying" is not correct.
^ permalink raw reply
* Re: [PATCH 1/2 v2] Documentation/git-remote-helpers: explain how import works with multiple refs
From: Sverre Rabbelier @ 2011-09-01 16:59 UTC (permalink / raw)
To: Matthieu Moy; +Cc: git, gitster
In-Reply-To: <1314895778-17482-1-git-send-email-Matthieu.Moy@imag.fr>
Heya,
On Thu, Sep 1, 2011 at 18:49, Matthieu Moy <Matthieu.Moy@imag.fr> wrote:
> This is important for two reasons:
>
> * when two "import" lines follow each other, only one "done" command
> should be issued in the fast-import stream, not one per "import".
>
> * The blank line terminating an import command should not be confused
> with the one terminating the sequence of commands.
>
> While we're there, illustrate the corresponding explanation for push
> batches with an example.
>
> Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
Acked-by: Sverre Rabbelier <srabbelier@gmail.com>
--
Cheers,
Sverre Rabbelier
^ permalink raw reply
* Re: [PATCH v2] git svn : hook before 'git svn dcommit'
From: Paul Young @ 2011-09-01 16:58 UTC (permalink / raw)
To: git
In-Reply-To: <20110818091228.GB16549@m62s10.vlinux.de>
Hi there
I'm trying to set up a post git svn dcommit hook in order to automatically
deliver stories via Pivotal Tracker:
https://www.pivotaltracker.com/help/api?version=v3#subversion_post_commit_example
Do you think you could help?
Thanks.
^ permalink raw reply
* Re: [PATCH 2/2 v2] (short) documentation for the testgit remote helper
From: Sverre Rabbelier @ 2011-09-01 16:59 UTC (permalink / raw)
To: Matthieu Moy; +Cc: git, gitster
In-Reply-To: <1314895778-17482-2-git-send-email-Matthieu.Moy@imag.fr>
Heya,
On Thu, Sep 1, 2011 at 18:49, Matthieu Moy <Matthieu.Moy@imag.fr> wrote:
> While it's not a command meant to be used by actual users (hence, not
> mentionned in git(1)), this command is a very precious help for
> remote-helpers authors.
>
> The best place for such technical doc is the source code, but users may
> not find it without a link in a manpage.
>
> Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
Acked-by: Sverre Rabbelier <srabbelier@gmail.com>
--
Cheers,
Sverre Rabbelier
^ permalink raw reply
* Re: git-checkout silently throws away the dirty status of index without a warning?
From: Jeff King @ 2011-09-01 16:56 UTC (permalink / raw)
To: Tzu-Jung Lee; +Cc: git
In-Reply-To: <CAEvN+1h+mY+f3dzK7LFOwkqokOZSS-LosCzBqtYGbyjz=Dg7Zw@mail.gmail.com>
On Thu, Sep 01, 2011 at 11:47:59PM +0800, Tzu-Jung Lee wrote:
> Correct me if I'm wrong:
>
> git-checkout saves the changes to index and working-tree, and
> tries to apply them to the destined commit.
> If the changes are applicable, then git-checkout the destined
> commit and apply the changes.
> Otherwise, git-checkout fails with warnings and leaves the current
> status untouched.
Not exactly. "git checkout <branch>" will switch your HEAD to <branch>,
and then try to make your index and working tree match the contents of
<branch>, with two exceptions:
1. If you have local changes in a file, but the contents of the file
in <branch> do not differ from what's in the current HEAD, then the
file will be left alone (i.e., your local changes will be
preserved).
2. If you have local changes in a file, and the contents of the file
in <branch> differ both from what's in your working tree and from
what's in your current HEAD, git will print an error and refuse to
overwrite your changes (though you can ask it to merge them with
"git checkout -m").
So it is not about "do these changes apply", but rather that we will
give up any time file-level merging is required (unless "-m" is
specified).
The other form, "git checkout <branch> [--] <file>", is not about
switching branches at all, but about putting content from <branch> into
the current index and working tree, overwriting what's there.
> If the above correct. Please help me clarify if the following corner
> case an intended or unexpected behavior.
> [...]
> $ git checkout -b br1
> $ git reset HEAD^
> Unstaged changes after reset:
> M aaa.txt
> M bbb.txt
So you have changes in two commits...
> $ git checkout HEAD aaa.txt
And here you explicitly overwrite the changes in aaa.txt.
> $ git status --short
> M bbb.txt
...leaving only the changes in bbb.txt.
> $ git add bbb.txt
> $ git status
>
> # On branch br1
> # Changes to be committed:
> # (use "git reset HEAD <file>..." to unstage)
> #
> # modified: bbb.txt
> #
OK, now it's staged.
> $ git checkout master
> Switched to branch 'master'
>
> git silently switch to master without warning against the index are
> "RESTORE/RESET" to clean.
Yes, because the changes in your index were identical to what was in the
destination branch. So we didn't drop any changes; they're still in the
index and in the working tree. It's simply that when compared to your
new HEAD, they are uninteresting.
> $ git checkout br1
> $ git status
> # On branch br1
> nothing to commit (working directory clean)
And now when we switch to br1, you have no changes against master in
your working tree or index, so there is no dirty state to block
switching branches.
I think git is working as intended here. I agree it is a somewhat
surprising corner case, but only because your changes happened to
exactly match the difference between the two branches you are switching
between. But it makes sense when you think about what "dirty state"
means: it is differences between HEAD and your index and working tree.
So we usually think of creating or removing dirty state by changing the
working tree. But you could equally well do it by changing the HEAD
without changing the working tree, which is what you did here.
-Peff
^ permalink raw reply
* [PATCH 1/2 v7] Add a remote helper to interact with mediawiki (fetch & push)
From: Matthieu Moy @ 2011-09-01 16:54 UTC (permalink / raw)
To: git, gitster
Cc: Jeremie Nikaes, Arnaud Lacurie, Claire Fousse, David Amouyal,
Matthieu Moy, Sylvain Boulmé
In-Reply-To: <1314896096-17544-1-git-send-email-Matthieu.Moy@imag.fr>
From: Jeremie Nikaes <jeremie.nikaes@ensimag.imag.fr>
Implement a gate between git and mediawiki, allowing git users to push
and pull objects from mediawiki just as one would do with a classic git
repository thanks to remote-helpers.
The following packages need to be installed (available on common
repositories):
libmediawiki-api-perl
libdatetime-format-iso8601-perl
Use remote helpers in order to be as transparent as possible to the git
user.
Download Mediawiki revisions through the Mediawiki API and then
fast-import into git.
Mediawiki revision number and git commits are linked thanks to notes
bound to commits.
The import part is done on a refs/mediawiki/<remote> branch before
coming to refs/remote/origin/master (Huge thanks to Jonathan Nieder
for his help)
We use UTF-8 everywhere: use encoding 'utf8'; does most of the job, but
we also read the output of Git commands in UTF-8 with the small helper
run_git, and write to the console (STDERR) in UTF-8. This allows a
seamless use of non-ascii characters in page titles, but hasn't been
tested on non-UTF-8 systems. In particular, UTF-8 encoding for filenames
could raise problems if different file systems handle UTF-8 filenames
differently. A uri_escape of mediawiki filenames could be imaginable, and
is still to be discussed further.
Partial cloning is supported using one of:
git clone -c remote.origin.pages='A_Page Another_Page' mediawiki::http://wikiurl
git clone -c remote.origin.categories='Some_Category' mediawiki::http://wikiurl
git clone -c remote.origin.shallow='True' mediawiki::http://wikiurl
Thanks to notes metadata, it is possible to compare remote and local last
mediawiki revision to warn non-fast forward pushes and "everything
up-to-date" case.
When allowed, push looks for each commit between remotes/origin/master
and HEAD, catches every blob related to these commit and push them in
chronological order. To do so, it uses git rev-list --children HEAD and
travels the tree from remotes/origin/master to HEAD through children. In
other words:
* Shortest path from remotes/origin/master to HEAD
* For each commit encountered, push blobs related to this commit
Signed-off-by: Jérémie Nikaes <jeremie.nikaes@ensimag.imag.fr>
Signed-off-by: Arnaud Lacurie <arnaud.lacurie@ensimag.imag.fr>
Signed-off-by: Claire Fousse <claire.fousse@ensimag.imag.fr>
Signed-off-by: David Amouyal <david.amouyal@ensimag.imag.fr>
Signed-off-by: Matthieu Moy <matthieu.moy@grenoble-inp.fr>
Signed-off-by: Sylvain Boulmé <sylvain.boulme@imag.fr>
---
Changes since v6:
* Regexp match simplification, thanks to Junio
* Trivial whitespace fixes, for consistancy.
contrib/mw-to-git/git-remote-mediawiki | 756 ++++++++++++++++++++++++++++
contrib/mw-to-git/git-remote-mediawiki.txt | 7 +
2 files changed, 763 insertions(+), 0 deletions(-)
create mode 100755 contrib/mw-to-git/git-remote-mediawiki
create mode 100644 contrib/mw-to-git/git-remote-mediawiki.txt
diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki
new file mode 100755
index 0000000..d4d1198
--- /dev/null
+++ b/contrib/mw-to-git/git-remote-mediawiki
@@ -0,0 +1,756 @@
+#! /usr/bin/perl
+
+# Copyright (C) 2011
+# Jérémie Nikaes <jeremie.nikaes@ensimag.imag.fr>
+# Arnaud Lacurie <arnaud.lacurie@ensimag.imag.fr>
+# Claire Fousse <claire.fousse@ensimag.imag.fr>
+# David Amouyal <david.amouyal@ensimag.imag.fr>
+# Matthieu Moy <matthieu.moy@grenoble-inp.fr>
+# License: GPL v2 or later
+
+# Gateway between Git and MediaWiki.
+# https://github.com/Bibzball/Git-Mediawiki/wiki
+#
+# Known limitations:
+#
+# - Only wiki pages are managed, no support for [[File:...]]
+# attachments.
+#
+# - Poor performance in the best case: it takes forever to check
+# whether we're up-to-date (on fetch or push) or to fetch a few
+# revisions from a large wiki, because we use exclusively a
+# page-based synchronization. We could switch to a wiki-wide
+# synchronization when the synchronization involves few revisions
+# but the wiki is large.
+#
+# - Git renames could be turned into MediaWiki renames (see TODO
+# below)
+#
+# - login/password support requires the user to write the password
+# cleartext in a file (see TODO below).
+#
+# - No way to import "one page, and all pages included in it"
+#
+# - Multiple remote MediaWikis have not been very well tested.
+
+use strict;
+use MediaWiki::API;
+use DateTime::Format::ISO8601;
+use encoding 'utf8';
+
+# use encoding 'utf8' doesn't change STDERROR
+# but we're going to output UTF-8 filenames to STDERR
+binmode STDERR, ":utf8";
+
+use URI::Escape;
+use warnings;
+
+# Mediawiki filenames can contain forward slashes. This variable decides by which pattern they should be replaced
+use constant SLASH_REPLACEMENT => "%2F";
+
+# It's not always possible to delete pages (may require some
+# priviledges). Deleted pages are replaced with this content.
+use constant DELETED_CONTENT => "[[Category:Deleted]]\n";
+
+# It's not possible to create empty pages. New empty files in Git are
+# sent with this content instead.
+use constant EMPTY_CONTENT => "<!-- empty page -->\n";
+
+# used to reflect file creation or deletion in diff.
+use constant NULL_SHA1 => "0000000000000000000000000000000000000000";
+
+my $remotename = $ARGV[0];
+my $url = $ARGV[1];
+
+# Accept both space-separated and multiple keys in config file.
+# Spaces should be written as _ anyway because we'll use chomp.
+my @tracked_pages = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".pages"));
+chomp(@tracked_pages);
+
+# Just like @tracked_pages, but for MediaWiki categories.
+my @tracked_categories = split(/[ \n]/, run_git("config --get-all remote.". $remotename .".categories"));
+chomp(@tracked_categories);
+
+my $wiki_login = run_git("config --get remote.". $remotename .".mwLogin");
+# TODO: ideally, this should be able to read from keyboard, but we're
+# inside a remote helper, so our stdin is connect to git, not to a
+# terminal.
+my $wiki_passwd = run_git("config --get remote.". $remotename .".mwPassword");
+chomp($wiki_login);
+chomp($wiki_passwd);
+
+# Import only last revisions (both for clone and fetch)
+my $shallow_import = run_git("config --get --bool remote.". $remotename .".shallow");
+chomp($shallow_import);
+$shallow_import = ($shallow_import eq "true");
+
+my $wiki_name = $url;
+$wiki_name =~ s/[^\/]*:\/\///;
+
+# Commands parser
+my $entry;
+my @cmd;
+while (<STDIN>) {
+ chomp;
+ @cmd = split(/ /);
+ if (defined($cmd[0])) {
+ # Line not blank
+ if ($cmd[0] eq "capabilities") {
+ die("Too many arguments for capabilities") unless (!defined($cmd[1]));
+ mw_capabilities();
+ } elsif ($cmd[0] eq "list") {
+ die("Too many arguments for list") unless (!defined($cmd[2]));
+ mw_list($cmd[1]);
+ } elsif ($cmd[0] eq "import") {
+ die("Invalid arguments for import") unless ($cmd[1] ne "" && !defined($cmd[2]));
+ mw_import($cmd[1]);
+ } elsif ($cmd[0] eq "option") {
+ die("Too many arguments for option") unless ($cmd[1] ne "" && $cmd[2] ne "" && !defined($cmd[3]));
+ mw_option($cmd[1],$cmd[2]);
+ } elsif ($cmd[0] eq "push") {
+ mw_push($cmd[1]);
+ } else {
+ print STDERR "Unknown command. Aborting...\n";
+ last;
+ }
+ } else {
+ # blank line: we should terminate
+ last;
+ }
+
+ BEGIN { $| = 1 } # flush STDOUT, to make sure the previous
+ # command is fully processed.
+}
+
+########################## Functions ##############################
+
+# MediaWiki API instance, created lazily.
+my $mediawiki;
+
+sub mw_connect_maybe {
+ if ($mediawiki) {
+ return;
+ }
+ $mediawiki = MediaWiki::API->new;
+ $mediawiki->{config}->{api_url} = "$url/api.php";
+ if ($wiki_login) {
+ if (!$mediawiki->login({
+ lgname => $wiki_login,
+ lgpassword => $wiki_passwd,
+ })) {
+ print STDERR "Failed to log in mediawiki user \"$wiki_login\" on $url\n";
+ print STDERR "(error " .
+ $mediawiki->{error}->{code} . ': ' .
+ $mediawiki->{error}->{details} . ")\n";
+ exit 1;
+ } else {
+ print STDERR "Logged in with user \"$wiki_login\".\n";
+ }
+ }
+}
+
+sub get_mw_first_pages {
+ my $some_pages = shift;
+ my @some_pages = @{$some_pages};
+
+ my $pages = shift;
+
+ # pattern 'page1|page2|...' required by the API
+ my $titles = join('|', @some_pages);
+
+ my $mw_pages = $mediawiki->api({
+ action => 'query',
+ titles => $titles,
+ });
+ if (!defined($mw_pages)) {
+ print STDERR "fatal: could not query the list of wiki pages.\n";
+ print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+ print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
+ exit 1;
+ }
+ while (my ($id, $page) = each(%{$mw_pages->{query}->{pages}})) {
+ if ($id < 0) {
+ print STDERR "Warning: page $page->{title} not found on wiki\n";
+ } else {
+ $pages->{$page->{title}} = $page;
+ }
+ }
+}
+
+sub get_mw_pages {
+ mw_connect_maybe();
+
+ my %pages; # hash on page titles to avoid duplicates
+ my $user_defined;
+ if (@tracked_pages) {
+ $user_defined = 1;
+ # The user provided a list of pages titles, but we
+ # still need to query the API to get the page IDs.
+
+ my @some_pages = @tracked_pages;
+ while (@some_pages) {
+ my $last = 50;
+ if ($#some_pages < $last) {
+ $last = $#some_pages;
+ }
+ my @slice = @some_pages[0..$last];
+ get_mw_first_pages(\@slice, \%pages);
+ @some_pages = @some_pages[51..$#some_pages];
+ }
+ }
+ if (@tracked_categories) {
+ $user_defined = 1;
+ foreach my $category (@tracked_categories) {
+ if (index($category, ':') < 0) {
+ # Mediawiki requires the Category
+ # prefix, but let's not force the user
+ # to specify it.
+ $category = "Category:" . $category;
+ }
+ my $mw_pages = $mediawiki->list( {
+ action => 'query',
+ list => 'categorymembers',
+ cmtitle => $category,
+ cmlimit => 'max' } )
+ || die $mediawiki->{error}->{code} . ': ' . $mediawiki->{error}->{details};
+ foreach my $page (@{$mw_pages}) {
+ $pages{$page->{title}} = $page;
+ }
+ }
+ }
+ if (!$user_defined) {
+ # No user-provided list, get the list of pages from
+ # the API.
+ my $mw_pages = $mediawiki->list({
+ action => 'query',
+ list => 'allpages',
+ aplimit => 500,
+ });
+ if (!defined($mw_pages)) {
+ print STDERR "fatal: could not get the list of wiki pages.\n";
+ print STDERR "fatal: '$url' does not appear to be a mediawiki\n";
+ print STDERR "fatal: make sure '$url/api.php' is a valid page.\n";
+ exit 1;
+ }
+ foreach my $page (@{$mw_pages}) {
+ $pages{$page->{title}} = $page;
+ }
+ }
+ return values(%pages);
+}
+
+sub run_git {
+ open(my $git, "-|:encoding(UTF-8)", "git " . $_[0]);
+ my $res = do { local $/; <$git> };
+ close($git);
+
+ return $res;
+}
+
+
+sub get_last_local_revision {
+ # Get note regarding last mediawiki revision
+ my $note = run_git("notes --ref=$remotename/mediawiki show refs/mediawiki/$remotename/master 2>/dev/null");
+ my @note_info = split(/ /, $note);
+
+ my $lastrevision_number;
+ if (!(defined($note_info[0]) && $note_info[0] eq "mediawiki_revision:")) {
+ print STDERR "No previous mediawiki revision found";
+ $lastrevision_number = 0;
+ } else {
+ # Notes are formatted : mediawiki_revision: #number
+ $lastrevision_number = $note_info[1];
+ chomp($lastrevision_number);
+ print STDERR "Last local mediawiki revision found is $lastrevision_number";
+ }
+ return $lastrevision_number;
+}
+
+sub get_last_remote_revision {
+ mw_connect_maybe();
+
+ my @pages = get_mw_pages();
+
+ my $max_rev_num = 0;
+
+ foreach my $page (@pages) {
+ my $id = $page->{pageid};
+
+ my $query = {
+ action => 'query',
+ prop => 'revisions',
+ rvprop => 'ids',
+ pageids => $id,
+ };
+
+ my $result = $mediawiki->api($query);
+
+ my $lastrev = pop(@{$result->{query}->{pages}->{$id}->{revisions}});
+
+ $max_rev_num = ($lastrev->{revid} > $max_rev_num ? $lastrev->{revid} : $max_rev_num);
+ }
+
+ print STDERR "Last remote revision found is $max_rev_num.\n";
+ return $max_rev_num;
+}
+
+# Clean content before sending it to MediaWiki
+sub mediawiki_clean {
+ my $string = shift;
+ my $page_created = shift;
+ # Mediawiki does not allow blank space at the end of a page and ends with a single \n.
+ # This function right trims a string and adds a \n at the end to follow this rule
+ $string =~ s/\s+$//;
+ if ($string eq "" && $page_created) {
+ # Creating empty pages is forbidden.
+ $string = EMPTY_CONTENT;
+ }
+ return $string."\n";
+}
+
+# Filter applied on MediaWiki data before adding them to Git
+sub mediawiki_smudge {
+ my $string = shift;
+ if ($string eq EMPTY_CONTENT) {
+ $string = "";
+ }
+ # This \n is important. This is due to mediawiki's way to handle end of files.
+ return $string."\n";
+}
+
+sub mediawiki_clean_filename {
+ my $filename = shift;
+ $filename =~ s/@{[SLASH_REPLACEMENT]}/\//g;
+ # [, ], |, {, and } are forbidden by MediaWiki, even URL-encoded.
+ # Do a variant of URL-encoding, i.e. looks like URL-encoding,
+ # but with _ added to prevent MediaWiki from thinking this is
+ # an actual special character.
+ $filename =~ s/[\[\]\{\}\|]/sprintf("_%%_%x", ord($&))/ge;
+ # If we use the uri escape before
+ # we should unescape here, before anything
+
+ return $filename;
+}
+
+sub mediawiki_smudge_filename {
+ my $filename = shift;
+ $filename =~ s/\//@{[SLASH_REPLACEMENT]}/g;
+ $filename =~ s/ /_/g;
+ # Decode forbidden characters encoded in mediawiki_clean_filename
+ $filename =~ s/_%_([0-9a-fA-F][0-9a-fA-F])/sprintf("%c", hex($1))/ge;
+ return $filename;
+}
+
+sub literal_data {
+ my ($content) = @_;
+ print STDOUT "data ", bytes::length($content), "\n", $content;
+}
+
+sub mw_capabilities {
+ # Revisions are imported to the private namespace
+ # refs/mediawiki/$remotename/ by the helper and fetched into
+ # refs/remotes/$remotename later by fetch.
+ print STDOUT "refspec refs/heads/*:refs/mediawiki/$remotename/*\n";
+ print STDOUT "import\n";
+ print STDOUT "list\n";
+ print STDOUT "push\n";
+ print STDOUT "\n";
+}
+
+sub mw_list {
+ # MediaWiki do not have branches, we consider one branch arbitrarily
+ # called master, and HEAD pointing to it.
+ print STDOUT "? refs/heads/master\n";
+ print STDOUT "\@refs/heads/master HEAD\n";
+ print STDOUT "\n";
+}
+
+sub mw_option {
+ print STDERR "remote-helper command 'option $_[0]' not yet implemented\n";
+ print STDOUT "unsupported\n";
+}
+
+sub fetch_mw_revisions_for_page {
+ my $page = shift;
+ my $id = shift;
+ my $fetch_from = shift;
+ my @page_revs = ();
+ my $query = {
+ action => 'query',
+ prop => 'revisions',
+ rvprop => 'ids',
+ rvdir => 'newer',
+ rvstartid => $fetch_from,
+ rvlimit => 500,
+ pageids => $id,
+ };
+
+ my $revnum = 0;
+ # Get 500 revisions at a time due to the mediawiki api limit
+ while (1) {
+ my $result = $mediawiki->api($query);
+
+ # Parse each of those 500 revisions
+ foreach my $revision (@{$result->{query}->{pages}->{$id}->{revisions}}) {
+ my $page_rev_ids;
+ $page_rev_ids->{pageid} = $page->{pageid};
+ $page_rev_ids->{revid} = $revision->{revid};
+ push(@page_revs, $page_rev_ids);
+ $revnum++;
+ }
+ last unless $result->{'query-continue'};
+ $query->{rvstartid} = $result->{'query-continue'}->{revisions}->{rvstartid};
+ }
+ if ($shallow_import && @page_revs) {
+ print STDERR " Found 1 revision (shallow import).\n";
+ @page_revs = sort {$b->{revid} <=> $a->{revid}} (@page_revs);
+ return $page_revs[0];
+ }
+ print STDERR " Found ", $revnum, " revision(s).\n";
+ return @page_revs;
+}
+
+sub fetch_mw_revisions {
+ my $pages = shift; my @pages = @{$pages};
+ my $fetch_from = shift;
+
+ my @revisions = ();
+ my $n = 1;
+ foreach my $page (@pages) {
+ my $id = $page->{pageid};
+
+ print STDERR "page $n/", scalar(@pages), ": ". $page->{title} ."\n";
+ $n++;
+ my @page_revs = fetch_mw_revisions_for_page($page, $id, $fetch_from);
+ @revisions = (@page_revs, @revisions);
+ }
+
+ return ($n, @revisions);
+}
+
+sub import_file_revision {
+ my $commit = shift;
+ my %commit = %{$commit};
+ my $full_import = shift;
+ my $n = shift;
+
+ my $title = $commit{title};
+ my $comment = $commit{comment};
+ my $content = $commit{content};
+ my $author = $commit{author};
+ my $date = $commit{date};
+
+ print STDOUT "commit refs/mediawiki/$remotename/master\n";
+ print STDOUT "mark :$n\n";
+ print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n";
+ literal_data($comment);
+
+ # If it's not a clone, we need to know where to start from
+ if (!$full_import && $n == 1) {
+ print STDOUT "from refs/mediawiki/$remotename/master^0\n";
+ }
+ if ($content ne DELETED_CONTENT) {
+ print STDOUT "M 644 inline $title.mw\n";
+ literal_data($content);
+ print STDOUT "\n\n";
+ } else {
+ print STDOUT "D $title.mw\n";
+ }
+
+ # mediawiki revision number in the git note
+ if ($full_import && $n == 1) {
+ print STDOUT "reset refs/notes/$remotename/mediawiki\n";
+ }
+ print STDOUT "commit refs/notes/$remotename/mediawiki\n";
+ print STDOUT "committer $author <$author\@$wiki_name> ", $date->epoch, " +0000\n";
+ literal_data("Note added by git-mediawiki during import");
+ if (!$full_import && $n == 1) {
+ print STDOUT "from refs/notes/$remotename/mediawiki^0\n";
+ }
+ print STDOUT "N inline :$n\n";
+ literal_data("mediawiki_revision: " . $commit{mw_revision});
+ print STDOUT "\n\n";
+}
+
+# parse a sequence of
+# <cmd> <arg1>
+# <cmd> <arg2>
+# \n
+# (like batch sequence of import and sequence of push statements)
+sub get_more_refs {
+ my $cmd = shift;
+ my @refs;
+ while (1) {
+ my $line = <STDIN>;
+ if ($line =~ m/^$cmd (.*)$/) {
+ push(@refs, $1);
+ } elsif ($line eq "\n") {
+ return @refs;
+ } else {
+ die("Invalid command in a '$cmd' batch: ". $_);
+ }
+ }
+}
+
+sub mw_import {
+ # multiple import commands can follow each other.
+ my @refs = (shift, get_more_refs("import"));
+ foreach my $ref (@refs) {
+ mw_import_ref($ref);
+ }
+ print STDOUT "done\n";
+}
+
+sub mw_import_ref {
+ my $ref = shift;
+ # The remote helper will call "import HEAD" and
+ # "import refs/heads/master".
+ # Since HEAD is a symbolic ref to master (by convention,
+ # followed by the output of the command "list" that we gave),
+ # we don't need to do anything in this case.
+ if ($ref eq "HEAD") {
+ return;
+ }
+
+ mw_connect_maybe();
+
+ my @pages = get_mw_pages();
+
+ print STDERR "Searching revisions...\n";
+ my $last_local = get_last_local_revision();
+ my $fetch_from = $last_local + 1;
+ if ($fetch_from == 1) {
+ print STDERR ", fetching from beginning.\n";
+ } else {
+ print STDERR ", fetching from here.\n";
+ }
+ my ($n, @revisions) = fetch_mw_revisions(\@pages, $fetch_from);
+
+ # Creation of the fast-import stream
+ print STDERR "Fetching & writing export data...\n";
+
+ $n = 0;
+ my $last_timestamp = 0; # Placeholer in case $rev->timestamp is undefined
+
+ foreach my $pagerevid (sort {$a->{revid} <=> $b->{revid}} @revisions) {
+ # fetch the content of the pages
+ my $query = {
+ action => 'query',
+ prop => 'revisions',
+ rvprop => 'content|timestamp|comment|user|ids',
+ revids => $pagerevid->{revid},
+ };
+
+ my $result = $mediawiki->api($query);
+
+ my $rev = pop(@{$result->{query}->{pages}->{$pagerevid->{pageid}}->{revisions}});
+
+ $n++;
+
+ my %commit;
+ $commit{author} = $rev->{user} || 'Anonymous';
+ $commit{comment} = $rev->{comment} || '*Empty MediaWiki Message*';
+ $commit{title} = mediawiki_smudge_filename(
+ $result->{query}->{pages}->{$pagerevid->{pageid}}->{title}
+ );
+ $commit{mw_revision} = $pagerevid->{revid};
+ $commit{content} = mediawiki_smudge($rev->{'*'});
+
+ if (!defined($rev->{timestamp})) {
+ $last_timestamp++;
+ } else {
+ $last_timestamp = $rev->{timestamp};
+ }
+ $commit{date} = DateTime::Format::ISO8601->parse_datetime($last_timestamp);
+
+ print STDERR "$n/", scalar(@revisions), ": Revision #$pagerevid->{revid} of $commit{title}\n";
+
+ import_file_revision(\%commit, ($fetch_from == 1), $n);
+ }
+
+ if ($fetch_from == 1 && $n == 0) {
+ print STDERR "You appear to have cloned an empty MediaWiki.\n";
+ # Something has to be done remote-helper side. If nothing is done, an error is
+ # thrown saying that HEAD is refering to unknown object 0000000000000000000
+ # and the clone fails.
+ }
+}
+
+sub error_non_fast_forward {
+ # Native git-push would show this after the summary.
+ # We can't ask it to display it cleanly, so print it
+ # ourselves before.
+ print STDERR "To prevent you from losing history, non-fast-forward updates were rejected\n";
+ print STDERR "Merge the remote changes (e.g. 'git pull') before pushing again. See the\n";
+ print STDERR "'Note about fast-forwards' section of 'git push --help' for details.\n";
+
+ print STDOUT "error $_[0] \"non-fast-forward\"\n";
+ return 0;
+}
+
+sub mw_push_file {
+ my $diff_info = shift;
+ # $diff_info contains a string in this format:
+ # 100644 100644 <sha1_of_blob_before_commit> <sha1_of_blob_now> <status>
+ my @diff_info_split = split(/[ \t]/, $diff_info);
+
+ # Filename, including .mw extension
+ my $complete_file_name = shift;
+ # Commit message
+ my $summary = shift;
+
+ my $new_sha1 = $diff_info_split[3];
+ my $old_sha1 = $diff_info_split[2];
+ my $page_created = ($old_sha1 eq NULL_SHA1);
+ my $page_deleted = ($new_sha1 eq NULL_SHA1);
+ $complete_file_name = mediawiki_clean_filename($complete_file_name);
+
+ if (substr($complete_file_name,-3) eq ".mw") {
+ my $title = substr($complete_file_name,0,-3);
+
+ my $file_content;
+ if ($page_deleted) {
+ # Deleting a page usually requires
+ # special priviledges. A common
+ # convention is to replace the page
+ # with this content instead:
+ $file_content = DELETED_CONTENT;
+ } else {
+ $file_content = run_git("cat-file blob $new_sha1");
+ }
+
+ mw_connect_maybe();
+
+ my $result = $mediawiki->edit( {
+ action => 'edit',
+ summary => $summary,
+ title => $title,
+ text => mediawiki_clean($file_content, $page_created),
+ }, {
+ skip_encoding => 1 # Helps with names with accentuated characters
+ }) || die 'Fatal: Error ' .
+ $mediawiki->{error}->{code} .
+ ' from mediwiki: ' . $mediawiki->{error}->{details};
+ print STDERR "Pushed file : $new_sha1 - $title\n";
+ } else {
+ print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n"
+ }
+}
+
+sub mw_push {
+ # multiple push statements can follow each other
+ my @refsspecs = (shift, get_more_refs("push"));
+ my %status;
+ my $pushed;
+ for my $refspec (@refsspecs) {
+ my ($force, $local, $remote) = $refspec =~ /^(\+)?([^:]*):([^:]*)$/
+ or die("Invalid refspec for push. Expected <src>:<dst> or +<src>:<dst>");
+ if ($force) {
+ print STDERR "Warning: forced push not allowed on a MediaWiki.\n";
+ }
+ if ($local eq "") {
+ print STDERR "Cannot delete remote branch on a MediaWiki\n";
+ print STDOUT "error $remote cannot delete\n";
+ next;
+ }
+ if ($remote ne "refs/heads/master") {
+ print STDERR "Only push to the branch 'master' is supported on a MediaWiki\n";
+ print STDOUT "error $remote only master allowed\n";
+ next;
+ }
+ if (mw_push_revision($local, $remote)) {
+ $pushed = 1;
+ }
+ }
+
+ # Notify Git that the push is done
+ print STDOUT "\n";
+
+ if ($pushed) {
+ print STDERR "Just pushed some revisions to MediaWiki.\n";
+ print STDERR "The pushed revisions now have to be re-imported, and your current branch\n";
+ print STDERR "needs to be updated with these re-imported commits. You can do this with\n";
+ print STDERR "\n";
+ print STDERR " git pull --rebase\n";
+ print STDERR "\n";
+ }
+}
+
+sub mw_push_revision {
+ my $local = shift;
+ my $remote = shift; # actually, this has to be "refs/heads/master" at this point.
+ my $last_local_revid = get_last_local_revision();
+ print STDERR ".\n"; # Finish sentence started by get_last_local_revision()
+ my $last_remote_revid = get_last_remote_revision();
+
+ # Get sha1 of commit pointed by local HEAD
+ my $HEAD_sha1 = run_git("rev-parse $local 2>/dev/null"); chomp($HEAD_sha1);
+ # Get sha1 of commit pointed by remotes/$remotename/master
+ my $remoteorigin_sha1 = run_git("rev-parse refs/remotes/$remotename/master 2>/dev/null");
+ chomp($remoteorigin_sha1);
+
+ if ($last_local_revid > 0 &&
+ $last_local_revid < $last_remote_revid) {
+ return error_non_fast_forward($remote);
+ }
+
+ if ($HEAD_sha1 eq $remoteorigin_sha1) {
+ # nothing to push
+ return 0;
+ }
+
+ # Get every commit in between HEAD and refs/remotes/origin/master,
+ # including HEAD and refs/remotes/origin/master
+ my @commit_pairs = ();
+ if ($last_local_revid > 0) {
+ my $parsed_sha1 = $remoteorigin_sha1;
+ # Find a path from last MediaWiki commit to pushed commit
+ while ($parsed_sha1 ne $HEAD_sha1) {
+ my @commit_info = grep(/^$parsed_sha1/, split(/\n/, run_git("rev-list --children $local")));
+ if (!@commit_info) {
+ return error_non_fast_forward($remote);
+ }
+ my @commit_info_split = split(/ |\n/, $commit_info[0]);
+ # $commit_info_split[1] is the sha1 of the commit to export
+ # $commit_info_split[0] is the sha1 of its direct child
+ push(@commit_pairs, \@commit_info_split);
+ $parsed_sha1 = $commit_info_split[1];
+ }
+ } else {
+ # No remote mediawiki revision. Export the whole
+ # history (linearized with --first-parent)
+ print STDERR "Warning: no common ancestor, pushing complete history\n";
+ my $history = run_git("rev-list --first-parent --children $local");
+ my @history = split('\n', $history);
+ @history = @history[1..$#history];
+ foreach my $line (reverse @history) {
+ my @commit_info_split = split(/ |\n/, $line);
+ push(@commit_pairs, \@commit_info_split);
+ }
+ }
+
+ foreach my $commit_info_split (@commit_pairs) {
+ my $sha1_child = @{$commit_info_split}[0];
+ my $sha1_commit = @{$commit_info_split}[1];
+ my $diff_infos = run_git("diff-tree -r --raw -z $sha1_child $sha1_commit");
+ # TODO: we could detect rename, and encode them with a #redirect on the wiki.
+ # TODO: for now, it's just a delete+add
+ my @diff_info_list = split(/\0/, $diff_infos);
+ # Keep the first line of the commit message as mediawiki comment for the revision
+ my $commit_msg = (split(/\n/, run_git("show --pretty=format:\"%s\" $sha1_commit")))[0];
+ chomp($commit_msg);
+ # Push every blob
+ while (@diff_info_list) {
+ # git diff-tree -z gives an output like
+ # <metadata>\0<filename1>\0
+ # <metadata>\0<filename2>\0
+ # and we've split on \0.
+ my $info = shift(@diff_info_list);
+ my $file = shift(@diff_info_list);
+ mw_push_file($info, $file, $commit_msg);
+ }
+ }
+
+ print STDOUT "ok $remote\n";
+ return 1;
+}
diff --git a/contrib/mw-to-git/git-remote-mediawiki.txt b/contrib/mw-to-git/git-remote-mediawiki.txt
new file mode 100644
index 0000000..4d211f5
--- /dev/null
+++ b/contrib/mw-to-git/git-remote-mediawiki.txt
@@ -0,0 +1,7 @@
+Git-Mediawiki is a project which aims the creation of a gate
+between git and mediawiki, allowing git users to push and pull
+objects from mediawiki just as one would do with a classic git
+repository thanks to remote-helpers.
+
+For more information, visit the wiki at
+https://github.com/Bibzball/Git-Mediawiki/wiki
--
1.7.7.rc0.75.g56f27
^ permalink raw reply related
* [PATCH 2/2] git-remote-mediawiki: allow push to set MediaWiki metadata
From: Matthieu Moy @ 2011-09-01 16:54 UTC (permalink / raw)
To: git, gitster; +Cc: Matthieu Moy
In-Reply-To: <1314896096-17544-1-git-send-email-Matthieu.Moy@imag.fr>
Push can not set the commit note "mediawiki_revision:" and update the
remote reference. This avoids having to "git pull --rebase" after each
push, and is probably more natural. Make it the default, but let it be
configurable with mediawiki.dumbPush or remote.<remotename>.dumbPush.
Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
contrib/mw-to-git/git-remote-mediawiki | 35 ++++++++++++++++++++++++++++++-
1 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/contrib/mw-to-git/git-remote-mediawiki b/contrib/mw-to-git/git-remote-mediawiki
index d4d1198..0ba88de 100755
--- a/contrib/mw-to-git/git-remote-mediawiki
+++ b/contrib/mw-to-git/git-remote-mediawiki
@@ -84,6 +84,27 @@ my $shallow_import = run_git("config --get --bool remote.". $remotename .".shall
chomp($shallow_import);
$shallow_import = ($shallow_import eq "true");
+# Dumb push: don't update notes and mediawiki ref to reflect the last push.
+#
+# Configurable with mediawiki.dumbPush, or per-remote with
+# remote.<remotename>.dumbPush.
+#
+# This means the user will have to re-import the just-pushed
+# revisions. On the other hand, this means that the Git revisions
+# corresponding to MediaWiki revisions are all imported from the wiki,
+# regardless of whether they were initially created in Git or from the
+# web interface, hence all users will get the same history (i.e. if
+# the push from Git to MediaWiki loses some information, everybody
+# will get the history with information lost). If the import is
+# deterministic, this means everybody gets the same sha1 for each
+# MediaWiki revision.
+my $dumb_push = run_git("config --get --bool remote.$remotename.dumbPush");
+unless ($dumb_push) {
+ $dumb_push = run_git("config --get --bool mediawiki.dumbPush");
+}
+chomp($dumb_push);
+$dumb_push = ($dumb_push eq "true");
+
my $wiki_name = $url;
$wiki_name =~ s/[^\/]*:\/\///;
@@ -598,6 +619,9 @@ sub mw_push_file {
my $complete_file_name = shift;
# Commit message
my $summary = shift;
+ # MediaWiki revision number. Keep the previous one by default,
+ # in case there's no edit to perform.
+ my $newrevid = shift;
my $new_sha1 = $diff_info_split[3];
my $old_sha1 = $diff_info_split[2];
@@ -631,10 +655,12 @@ sub mw_push_file {
}) || die 'Fatal: Error ' .
$mediawiki->{error}->{code} .
' from mediwiki: ' . $mediawiki->{error}->{details};
+ $newrevid = $result->{edit}->{newrevid};
print STDERR "Pushed file : $new_sha1 - $title\n";
} else {
print STDERR "$complete_file_name not a mediawiki file (Not pushable on this version of git-remote-mediawiki).\n"
}
+ return $newrevid;
}
sub mw_push {
@@ -666,7 +692,7 @@ sub mw_push {
# Notify Git that the push is done
print STDOUT "\n";
- if ($pushed) {
+ if ($pushed && $dumb_push) {
print STDERR "Just pushed some revisions to MediaWiki.\n";
print STDERR "The pushed revisions now have to be re-imported, and your current branch\n";
print STDERR "needs to be updated with these re-imported commits. You can do this with\n";
@@ -682,6 +708,7 @@ sub mw_push_revision {
my $last_local_revid = get_last_local_revision();
print STDERR ".\n"; # Finish sentence started by get_last_local_revision()
my $last_remote_revid = get_last_remote_revision();
+ my $mw_revision = $last_remote_revid;
# Get sha1 of commit pointed by local HEAD
my $HEAD_sha1 = run_git("rev-parse $local 2>/dev/null"); chomp($HEAD_sha1);
@@ -747,7 +774,11 @@ sub mw_push_revision {
# and we've split on \0.
my $info = shift(@diff_info_list);
my $file = shift(@diff_info_list);
- mw_push_file($info, $file, $commit_msg);
+ $mw_revision = mw_push_file($info, $file, $commit_msg, $mw_revision);
+ }
+ unless ($dumb_push) {
+ run_git("notes --ref=$remotename/mediawiki add -m \"mediawiki_revision: $mw_revision\" $sha1_commit");
+ run_git("update-ref -m \"Git-MediaWiki push\" refs/mediawiki/$remotename/master $sha1_commit $sha1_child");
}
}
--
1.7.7.rc0.75.g56f27
^ permalink raw reply related
* [PATCH 0/2] Git-MediaWiki
From: Matthieu Moy @ 2011-09-01 16:54 UTC (permalink / raw)
To: git, gitster; +Cc: Matthieu Moy
In-Reply-To: <vpq39ggomtu.fsf@bauges.imag.fr>
First patch is just a resend of v6 with style improvements (Junio's
remark, and whitespace fixes). The second patch is new, it can be
squashed into the first if needed, but I've left it appart since I
think "what to do with metadata on push?" is one of the crucial
questions to be answered when writting remote-helpers, so it may
(hopefully) lead to fruitfull discussions.
Jeremie Nikaes (1):
Add a remote helper to interact with mediawiki (fetch & push)
Matthieu Moy (1):
git-remote-mediawiki: allow push to set MediaWiki metadata
contrib/mw-to-git/git-remote-mediawiki | 787 ++++++++++++++++++++++++++++
contrib/mw-to-git/git-remote-mediawiki.txt | 7 +
2 files changed, 794 insertions(+), 0 deletions(-)
create mode 100755 contrib/mw-to-git/git-remote-mediawiki
create mode 100644 contrib/mw-to-git/git-remote-mediawiki.txt
--
1.7.7.rc0.75.g56f27
^ permalink raw reply
* [PATCH 2/2 v2] (short) documentation for the testgit remote helper
From: Matthieu Moy @ 2011-09-01 16:49 UTC (permalink / raw)
To: git, gitster; +Cc: Matthieu Moy
In-Reply-To: <1314895778-17482-1-git-send-email-Matthieu.Moy@imag.fr>
While it's not a command meant to be used by actual users (hence, not
mentionned in git(1)), this command is a very precious help for
remote-helpers authors.
The best place for such technical doc is the source code, but users may
not find it without a link in a manpage.
Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
change since v1: document environment variables for debugging, not
hacking the source code.
Documentation/git-remote-helpers.txt | 2 ++
Documentation/git-remote-testgit.txt | 30 ++++++++++++++++++++++++++++++
git-remote-testgit.py | 13 +++++++++++++
3 files changed, 45 insertions(+), 0 deletions(-)
create mode 100644 Documentation/git-remote-testgit.txt
diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt
index 526fc6a..674797c 100644
--- a/Documentation/git-remote-helpers.txt
+++ b/Documentation/git-remote-helpers.txt
@@ -362,6 +362,8 @@ SEE ALSO
--------
linkgit:git-remote[1]
+linkgit:git-remote-testgit[1]
+
GIT
---
Part of the linkgit:git[1] suite
diff --git a/Documentation/git-remote-testgit.txt b/Documentation/git-remote-testgit.txt
new file mode 100644
index 0000000..2a67d45
--- /dev/null
+++ b/Documentation/git-remote-testgit.txt
@@ -0,0 +1,30 @@
+git-remote-testgit(1)
+=====================
+
+NAME
+----
+git-remote-testgit - Example remote-helper
+
+
+SYNOPSIS
+--------
+[verse]
+git clone testgit::<source-repo> [<destination>]
+
+DESCRIPTION
+-----------
+
+This command is a simple remote-helper, that is used both as a
+testcase for the remote-helper functionality, and as an example to
+show remote-helper authors one possible implementation.
+
+The best way to learn more is to read the comments and source code in
+'git-remote-testgit.py'.
+
+SEE ALSO
+--------
+linkgit:git-remote-helpers[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/git-remote-testgit.py b/git-remote-testgit.py
index e9c832b..3dc4851 100644
--- a/git-remote-testgit.py
+++ b/git-remote-testgit.py
@@ -1,5 +1,18 @@
#!/usr/bin/env python
+# This command is a simple remote-helper, that is used both as a
+# testcase for the remote-helper functionality, and as an example to
+# show remote-helper authors one possible implementation.
+#
+# This is a Git <-> Git importer/exporter, that simply uses git
+# fast-import and git fast-export to consume and produce fast-import
+# streams.
+#
+# To understand better the way things work, one can activate debug
+# traces by setting (to any value) the environment variables
+# GIT_TRANSPORT_HELPER_DEBUG and GIT_DEBUG_TESTGIT, to see messages
+# from the transport-helper side, or from this example remote-helper.
+
# hashlib is only available in python >= 2.5
try:
import hashlib
--
1.7.7.rc0.75.g56f27
^ permalink raw reply related
* [PATCH 1/2 v2] Documentation/git-remote-helpers: explain how import works with multiple refs
From: Matthieu Moy @ 2011-09-01 16:49 UTC (permalink / raw)
To: git, gitster; +Cc: Matthieu Moy
In-Reply-To: <vpqd3fk1cq5.fsf@bauges.imag.fr>
This is important for two reasons:
* when two "import" lines follow each other, only one "done" command
should be issued in the fast-import stream, not one per "import".
* The blank line terminating an import command should not be confused
with the one terminating the sequence of commands.
While we're there, illustrate the corresponding explanation for push
batches with an example.
Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr>
---
No real change since v1, but resending together with the other to make
it a "patch serie" ;-)
Documentation/git-remote-helpers.txt | 22 +++++++++++++++++++++-
1 files changed, 21 insertions(+), 1 deletions(-)
diff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt
index 760b164..526fc6a 100644
--- a/Documentation/git-remote-helpers.txt
+++ b/Documentation/git-remote-helpers.txt
@@ -241,7 +241,22 @@ Supported if the helper has the "fetch" capability.
'push' +<src>:<dst>::
Pushes the given local <src> commit or branch to the
remote branch described by <dst>. A batch sequence of
- one or more push commands is terminated with a blank line.
+ one or more 'push' commands is terminated with a blank line
+ (if there is only one reference to push, a single 'push' command
+ is followed by a blank line). For example, the following would
+ be two batches of 'push', the first asking the remote-helper
+ to push the local ref 'master' to the remote ref 'master' and
+ the local 'HEAD' to the remote 'branch', and the second
+ asking to push ref 'foo' to ref 'bar' (forced update requested
+ by the '+').
++
+------------
+push refs/heads/master:refs/heads/master
+push HEAD:refs/heads/branch
+\n
+push +refs/heads/foo:refs/heads/bar
+\n
+------------
+
Zero or more protocol options may be entered after the last 'push'
command, before the batch's terminating blank line.
@@ -266,6 +281,11 @@ Supported if the helper has the "push" capability.
Especially useful for interoperability with a foreign versioning
system.
+
+Just like 'push', a batch sequence of one or more 'import' is
+terminated with a blank line. For each batch of 'import', the remote
+helper should produce a fast-import stream terminated by a 'done'
+command.
++
Supported if the helper has the "import" capability.
'connect' <service>::
--
1.7.7.rc0.75.g56f27
^ permalink raw reply related
* [PATCH] for-each-ref: add split message parts to %(contents:*).
From: Michał Górny @ 2011-09-01 16:50 UTC (permalink / raw)
To: git; +Cc: Junio C Hamano, Michael J Gruber, Jeff King,
Michał Górny
In-Reply-To: <20110901184815.2cd8b472@pomiocik.lan>
Now %(contents:subject) contains the message subject, %(contents:body)
main body part and %(contents:signature) GPG signature.
---
Documentation/git-for-each-ref.txt | 7 +++--
builtin/for-each-ref.c | 42 ++++++++++++++++++++++++------------
2 files changed, 32 insertions(+), 17 deletions(-)
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
index 152e695..c872b88 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -101,9 +101,10 @@ Fields that have name-email-date tuple as its value (`author`,
`committer`, and `tagger`) can be suffixed with `name`, `email`,
and `date` to extract the named component.
-The first line of the message in a commit and tag object is
-`subject`, the remaining lines are `body`. The whole message
-is `contents`.
+The complete message in a commit and tag object is `contents`.
+Its first line is `contents:subject`, the remaining lines
+are `contents:body` and the optional GPG signature
+is `contents:signature`.
For sorting purposes, fields with numeric values sort in numeric
order (`objectsize`, `authordate`, `committerdate`, `taggerdate`).
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 89e75c6..e320ba2 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -69,6 +69,9 @@ static struct {
{ "subject" },
{ "body" },
{ "contents" },
+ { "contents:subject" },
+ { "contents:body" },
+ { "contents:signature" },
{ "upstream" },
{ "symref" },
{ "flag" },
@@ -458,8 +461,9 @@ static void grab_person(const char *who, struct atom_value *val, int deref, stru
}
}
-static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body)
+static void find_subpos(const char *buf, unsigned long sz, const char **sub, const char **body, const char **signature)
{
+ *signature = buf + parse_signature(buf, sz);
while (*buf) {
const char *eol = strchr(buf, '\n');
if (!eol)
@@ -475,21 +479,21 @@ static void find_subpos(const char *buf, unsigned long sz, const char **sub, con
if (!*buf)
return;
*sub = buf; /* first non-empty line */
- buf = strchr(buf, '\n');
- if (!buf) {
- *body = "";
- return; /* no body */
- }
- while (*buf == '\n')
- buf++; /* skip blank between subject and body */
- *body = buf;
+ buf = format_subject(NULL, buf, NULL);
+
+ /* When having a signed tag without body, format_subject()
+ * will start to eat the signature. */
+ if (buf > *signature)
+ *body = *signature;
+ else /* - 1 to get a trailing newline to strip */
+ *body = buf - 1;
}
/* See grab_values */
static void grab_sub_body_contents(struct atom_value *val, int deref, struct object *obj, void *buf, unsigned long sz)
{
int i;
- const char *subpos = NULL, *bodypos = NULL;
+ const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL;
for (i = 0; i < used_atom_cnt; i++) {
const char *name = used_atom[i];
@@ -500,19 +504,29 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, struct obj
name++;
if (strcmp(name, "subject") &&
strcmp(name, "body") &&
- strcmp(name, "contents"))
+ strcmp(name, "contents") &&
+ strcmp(name, "contents:subject") &&
+ strcmp(name, "contents:body") &&
+ strcmp(name, "contents:signature"))
continue;
if (!subpos)
- find_subpos(buf, sz, &subpos, &bodypos);
+ find_subpos(buf, sz, &subpos, &bodypos, &sigpos);
if (!subpos)
return;
- if (!strcmp(name, "subject"))
- v->s = copy_line(subpos);
+ if (!strcmp(name, "subject") || !strcmp(name, "contents:subject"))
+ v->s = xstrndup(subpos, bodypos - subpos - 1);
else if (!strcmp(name, "body"))
v->s = xstrdup(bodypos);
else if (!strcmp(name, "contents"))
v->s = xstrdup(subpos);
+ else if (!strcmp(name, "contents:body")) {
+ if (sigpos - bodypos > 0)
+ v->s = xstrndup(bodypos + 1, sigpos - bodypos - 1);
+ else
+ v->s = xstrdup("");
+ } else if (!strcmp(name, "contents:signature"))
+ v->s = xstrdup(sigpos);
}
}
--
1.7.6.1
^ 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