Git development
 help / color / mirror / Atom feed
* [PATCH] difftool: use perl built-ins when testing for msys
From: David Aguilar @ 2009-03-25  6:13 UTC (permalink / raw)
  To: gitster; +Cc: git, David Aguilar

I don't even know what $COMSPEC means so let's be safe and use the
same perly $^O test add--interactive uses.  While we're at it, make
git-difftool match the prevalent git-perl style.

Signed-off-by: David Aguilar <davvid@gmail.com>
---
 git-difftool.perl |    7 +++++--
 1 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/git-difftool.perl b/git-difftool.perl
index 0deda3a..207dd50 100755
--- a/git-difftool.perl
+++ b/git-difftool.perl
@@ -33,7 +33,10 @@ sub setup_environment
 sub exe
 {
 	my $exe = shift;
-	return defined $ENV{COMSPEC} ? "$exe.exe" : $exe;
+	if ($^O eq 'MSWin32' || $^O eq 'msys') {
+		return "$exe.exe";
+	}
+	return $exe;
 }
 
 sub generate_command
@@ -47,7 +50,7 @@ sub generate_command
 			$skip_next = 0;
 			next;
 		}
-		if ($arg eq '-t' or $arg eq '--tool') {
+		if ($arg eq '-t' || $arg eq '--tool') {
 			usage() if $#ARGV <= $idx;
 			$ENV{GIT_DIFF_TOOL} = $ARGV[$idx + 1];
 			$skip_next = 1;
-- 
1.6.2.1.303.g63699

^ permalink raw reply related

* Re: [PATCH] init-db: support --import to add all files and commit right after init
From: Jeff King @ 2009-03-25  4:49 UTC (permalink / raw)
  To: Nguyen Thai Ngoc Duy; +Cc: git
In-Reply-To: <fcaeb9bf0903242132k71e78219xfda5553854df4a2c@mail.gmail.com>

On Wed, Mar 25, 2009 at 03:32:17PM +1100, Nguyen Thai Ngoc Duy wrote:

> I tend to do "commit -q -m i", not a big deal for two more keystrokes.
> My initial version allowed "git init /path/to/workdir" too so it might
> be ambiguous for optional message. So all vote for "Initial commit"?
> Dynamic message seems overkill because most of the cases, this is a
> throw away repository.

Initial commit is fine with me. I plan to use this mostly for throw-away
repositories. I was thinking it might be useful for this:

  tar xzf package-0.1.tar.gz
  cd package-0.1
  git init --import

so that later you remember it came from package-0.1 (when you are
looking at package 0.2). OTOH, if we take the information from the
directory, then the fact that it came from 0.1 is by definition already
in the directory name.  So it is pretty pointless unless you later
rename the directory to just "package".

> > I seem to recall that we were phasing out "--long-option <arg>" at some
> > point, and that all long-options should use "--long-option=". But maybe
> > I am mis-remembering.
> 
> I did not know too. Any reference?

I was, in fact, mis-remembering. The problem is not about long versus
short options, but rather about options which have an optional value.
See "Separating argument from the option" in "git help cli".

However, what we are discussing above _is_ making it an optional value,
in which case "--import <arg>" would have no value for "--import".

-Peff

^ permalink raw reply

* Re: [PATCH] init-db: support --import to add all files and commit  right after init
From: Nguyen Thai Ngoc Duy @ 2009-03-25  4:32 UTC (permalink / raw)
  To: Jeff King; +Cc: git
In-Reply-To: <20090325041934.GA15524@coredump.intra.peff.net>

2009/3/25 Jeff King <peff@peff.net>:
>> +-m <message>::
>> +--import=<message>::
>> +
>> +Commit everything to the newly initialized repository. This is equivalent to:
>
> Maybe this is being too lazy, but I think it would be useful to allow
> just "--import" without a message to default to "Initial commit",
> "initial import from `basename $PWD`", or something like that.

I tend to do "commit -q -m i", not a big deal for two more keystrokes.
My initial version allowed "git init /path/to/workdir" too so it might
be ambiguous for optional message. So all vote for "Initial commit"?
Dynamic message seems overkill because most of the cases, this is a
throw away repository.

>> +             else if (!strcmp(arg, "--import") || !strcmp(arg, "-m")) {
>> +                     if (i+1 >= argc)
>> +                             die("--import requires an import message");
>> +                     import_message = argv[2];
>> +                     i++;
>> +                     argv++;
>> +             }
>
> I seem to recall that we were phasing out "--long-option <arg>" at some
> point, and that all long-options should use "--long-option=". But maybe
> I am mis-remembering.

I did not know too. Any reference?
-- 
Duy

^ permalink raw reply

* Re: [PATCH 1/2] Documentation/Makefile: make most operations "quiet"
From: Jeff King @ 2009-03-25  4:28 UTC (permalink / raw)
  To: Chris Johnsen; +Cc: git
In-Reply-To: <1237954900-21161-1-git-send-email-chris_johnsen@pobox.com>

On Tue, Mar 24, 2009 at 11:21:39PM -0500, Chris Johnsen wrote:

>  technical/api-index.txt: technical/api-index-skel.txt \
> -	technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS))
> +	$(QUIET_GEN)technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS)) && \
>  	cd technical && sh ./api-index.sh

What's going on here? The line you remove is part of the dependencies,
but you replace it with a line of build instructions (and make barfs, of
course).

-Peff

^ permalink raw reply

* Re: [PATCH] init-db: support --import to add all files and commit  right after init
From: Nguyen Thai Ngoc Duy @ 2009-03-25  4:25 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git
In-Reply-To: <alpine.DEB.1.00.0903250450240.10279@pacific.mpi-cbg.de>

2009/3/25 Johannes Schindelin <Johannes.Schindelin@gmx.de>:
> But could you please say "init" in the subject instead of "init-db"?  The
> latter is just a historical wart we have to carry around.

OK. No problem.
-- 
Duy

^ permalink raw reply

* Re: [PATCH] git-cget: prints elements of C code in the git repository
From: Jeff King @ 2009-03-25  4:23 UTC (permalink / raw)
  To: Steven Tweed; +Cc: Mike Ralphson, Roel Kluin, Johannes Schindelin, git
In-Reply-To: <d9c1caea0903240933n6dea7ddcl90a5e105c2a45b52@mail.gmail.com>

On Tue, Mar 24, 2009 at 04:33:13PM +0000, Steven Tweed wrote:

> Speaking of wanting things to work with the actual repository , one
> thing that I've been meaning to continue work on if I get the time is
> basically a 'show me any commit diff's that involve string s' (ie, the
> locations in which a change involving s occurs rather than just
> 'current file contains s (in exactly the same ways the previous
> version did). I'm extremely unlikely to actually produce anything

How about "git log -S", or does that somehow not meet your needs (and if
not, how)?

-Peff

^ permalink raw reply

* [PATCH 2/2] Documentation/Makefile: break up texi pipeline
From: Chris Johnsen @ 2009-03-25  4:21 UTC (permalink / raw)
  To: git; +Cc: Jeff King, Chris Johnsen
In-Reply-To: <1237954900-21161-1-git-send-email-chris_johnsen@pobox.com>

Most shells define the exit value of a pipeline as the exit value
of the last process. For each texi rule, run the DOCBOOK2X_TEXI
tool and the "fixup" script in their own non-pipeline commands so
that make will notice an error exit code.

Signed-off-by: Chris Johnsen <chris_johnsen@pobox.com>

---

This textually depends on my "quiet doc gen" patch as it modifies
a couple of the same lines.
---
 Documentation/Makefile |   11 +++++++----
 1 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/Documentation/Makefile b/Documentation/Makefile
index 3f9bc01..372a2cc 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -177,7 +177,7 @@ cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT)
 
 clean:
 	$(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7
-	$(RM) *.texi *.texi+ git.info gitman.info
+	$(RM) *.texi *.texi+ *.texi++ git.info gitman.info
 	$(RM) howto-index.txt howto/*.html doc.dep
 	$(RM) technical/api-*.html technical/api-index.txt
 	$(RM) $(cmds_txt) *.made
@@ -220,8 +220,9 @@ git.info: user-manual.texi
 
 user-manual.texi: user-manual.xml
 	$(QUIET_DB2TEXI)$(RM) $@+ $@ && \
-	$(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout | \
-		$(PERL_PATH) fix-texi.perl >$@+ && \
+	$(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout >$@++ && \
+	$(PERL_PATH) fix-texi.perl <$@++ >$@+ && \
+	rm $@++ && \
 	mv $@+ $@
 
 user-manual.pdf: user-manual.xml
@@ -232,7 +233,9 @@ user-manual.pdf: user-manual.xml
 gitman.texi: $(MAN_XML) cat-texi.perl
 	$(QUIET_DB2TEXI)$(RM) $@+ $@ && \
 	($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \
-		--to-stdout $(xml);)) | $(PERL_PATH) cat-texi.perl $@ >$@+ && \
+		--to-stdout $(xml) &&) true) > $@++
+	$(PERL_PATH) cat-texi.perl $@ <$@++ >$@+ && \
+	rm $@++ && \
 	mv $@+ $@
 
 gitman.info: gitman.texi
-- 
1.6.2.1.315.g33192

^ permalink raw reply related

* [PATCH 1/2] Documentation/Makefile: make most operations "quiet"
From: Chris Johnsen @ 2009-03-25  4:21 UTC (permalink / raw)
  To: git; +Cc: Jeff King, Chris Johnsen
In-Reply-To: <20090324091836.GD1799@coredump.intra.peff.net>

This adapts the "quiet make" implementation from the main
Makefile.

Signed-off-by: Chris Johnsen <chris_johnsen@pobox.com>
---

There is a small conflict if this is applied on top of my
"docbook-xsl/asciidoc" cleanup. Just add the $(XMLTO_EXTRAS) back
into the command line given in this version.
---
 Documentation/Makefile |   81 +++++++++++++++++++++++++++++++----------------
 1 files changed, 53 insertions(+), 28 deletions(-)

diff --git a/Documentation/Makefile b/Documentation/Makefile
index 144ec32..3f9bc01 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -76,6 +76,31 @@ endif
 # yourself - yes, all 6 characters of it!
 #
 
+QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
+QUIET_SUBDIR1  =
+
+ifneq ($(findstring $(MAKEFLAGS),w),w)
+PRINT_DIR = --no-print-directory
+else # "make -w"
+NO_SUBDIR = :
+endif
+
+ifneq ($(findstring $(MAKEFLAGS),s),s)
+ifndef V
+	QUIET_ASCIIDOC	= @echo '   ' ASCIIDOC $@;
+	QUIET_XMLTO	= @echo '   ' XMLTO $@;
+	QUIET_DB2TEXI	= @echo '   ' DB2TEXI $@;
+	QUIET_MAKEINFO	= @echo '   ' MAKEINFO $@;
+	QUIET_DBLATEX	= @echo '   ' DBLATEX $@;
+	QUIET_GEN	= @echo '   ' GEN $@;
+	QUIET_STDERR	= 2> /dev/null
+	QUIET_SUBDIR0	= +@subdir=
+	QUIET_SUBDIR1	= ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
+			  $(MAKE) $(PRINT_DIR) -C $$subdir
+	export V
+endif
+endif
+
 all: html man
 
 html: $(DOC_HTML)
@@ -119,7 +144,7 @@ install-html: html
 	sh ./install-webdoc.sh $(DESTDIR)$(htmldir)
 
 ../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
-	$(MAKE) -C ../ GIT-VERSION-FILE
+	$(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE
 
 -include ../GIT-VERSION-FILE
 
@@ -127,8 +152,8 @@ install-html: html
 # Determine "include::" file references in asciidoc files.
 #
 doc.dep : $(wildcard *.txt) build-docdep.perl
-	$(RM) $@+ $@
-	$(PERL_PATH) ./build-docdep.perl >$@+
+	$(QUIET_GEN)$(RM) $@+ $@ && \
+	$(PERL_PATH) ./build-docdep.perl >$@+ $(QUIET_STDERR) && \
 	mv $@+ $@
 
 -include doc.dep
@@ -146,8 +171,8 @@ cmds_txt = cmds-ancillaryinterrogators.txt \
 $(cmds_txt): cmd-list.made
 
 cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT)
-	$(RM) $@
-	$(PERL_PATH) ./cmd-list.perl ../command-list.txt
+	$(QUIET_GEN)$(RM) $@ && \
+	$(PERL_PATH) ./cmd-list.perl ../command-list.txt $(QUIET_STDERR) && \
 	date >$@
 
 clean:
@@ -158,30 +183,30 @@ clean:
 	$(RM) $(cmds_txt) *.made
 
 $(MAN_HTML): %.html : %.txt
-	$(RM) $@+ $@
+	$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
 	$(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \
-		$(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $<
+		$(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \
 	mv $@+ $@
 
 %.1 %.5 %.7 : %.xml
-	$(RM) $@
+	$(QUIET_XMLTO)$(RM) $@ && \
 	xmlto -m $(MANPAGE_XSL) man $<
 
 %.xml : %.txt
-	$(RM) $@+ $@
+	$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
 	$(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf \
-		$(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $<
+		$(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \
 	mv $@+ $@
 
 user-manual.xml: user-manual.txt user-manual.conf
-	$(ASCIIDOC) -b docbook -d book $<
+	$(QUIET_ASCIIDOC)$(ASCIIDOC) -b docbook -d book $<
 
 technical/api-index.txt: technical/api-index-skel.txt \
-	technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS))
+	$(QUIET_GEN)technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS)) && \
 	cd technical && sh ./api-index.sh
 
 $(patsubst %,%.html,$(API_DOCS) technical/api-index): %.html : %.txt
-	$(ASCIIDOC) -b xhtml11 -f asciidoc.conf \
+	$(QUIET_ASCIIDOC)$(ASCIIDOC) -b xhtml11 -f asciidoc.conf \
 		$(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) $*.txt
 
 XSLT = docbook.xsl
@@ -191,46 +216,46 @@ user-manual.html: user-manual.xml
 	xsltproc $(XSLTOPTS) -o $@ $(XSLT) $<
 
 git.info: user-manual.texi
-	$(MAKEINFO) --no-split -o $@ user-manual.texi
+	$(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ user-manual.texi
 
 user-manual.texi: user-manual.xml
-	$(RM) $@+ $@
+	$(QUIET_DB2TEXI)$(RM) $@+ $@ && \
 	$(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout | \
-		$(PERL_PATH) fix-texi.perl >$@+
+		$(PERL_PATH) fix-texi.perl >$@+ && \
 	mv $@+ $@
 
 user-manual.pdf: user-manual.xml
-	$(RM) $@+ $@
-	$(DBLATEX) -o $@+ -p /etc/asciidoc/dblatex/asciidoc-dblatex.xsl -s /etc/asciidoc/dblatex/asciidoc-dblatex.sty $<
+	$(QUIET_DBLATEX)$(RM) $@+ $@ && \
+	$(DBLATEX) -o $@+ -p /etc/asciidoc/dblatex/asciidoc-dblatex.xsl -s /etc/asciidoc/dblatex/asciidoc-dblatex.sty $< && \
 	mv $@+ $@
 
 gitman.texi: $(MAN_XML) cat-texi.perl
-	$(RM) $@+ $@
+	$(QUIET_DB2TEXI)$(RM) $@+ $@ && \
 	($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \
-		--to-stdout $(xml);)) | $(PERL_PATH) cat-texi.perl $@ >$@+
+		--to-stdout $(xml);)) | $(PERL_PATH) cat-texi.perl $@ >$@+ && \
 	mv $@+ $@
 
 gitman.info: gitman.texi
-	$(MAKEINFO) --no-split --no-validate $*.texi
+	$(QUIET_MAKEINFO)$(MAKEINFO) --no-split --no-validate $*.texi
 
 $(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml
-	$(RM) $@+ $@
-	$(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+
+	$(QUIET_DB2TEXI)$(RM) $@+ $@ && \
+	$(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+ && \
 	mv $@+ $@
 
 howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
-	$(RM) $@+ $@
-	sh ./howto-index.sh $(wildcard howto/*.txt) >$@+
+	$(QUIET_GEN)$(RM) $@+ $@ && \
+	sh ./howto-index.sh $(wildcard howto/*.txt) >$@+ && \
 	mv $@+ $@
 
 $(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
-	$(ASCIIDOC) -b xhtml11 $*.txt
+	$(QUIET_ASCIIDOC)$(ASCIIDOC) -b xhtml11 $*.txt
 
 WEBDOC_DEST = /pub/software/scm/git/docs
 
 $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
-	$(RM) $@+ $@
-	sed -e '1,/^$$/d' $< | $(ASCIIDOC) -b xhtml11 - >$@+
+	$(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
+	sed -e '1,/^$$/d' $< | $(ASCIIDOC) -b xhtml11 - >$@+ && \
 	mv $@+ $@
 
 install-webdoc : html
-- 
1.6.2.1.315.g33192

^ permalink raw reply related

* Re: [PATCH] init-db: support --import to add all files and commit right after init
From: Jeff King @ 2009-03-25  4:19 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git
In-Reply-To: <1237946996-5287-1-git-send-email-pclouds@gmail.com>

On Wed, Mar 25, 2009 at 01:09:56PM +1100, Nguyễn Thái Ngọc Duy wrote:

> This is equivalent to "git init;git add .;git commit -q -m blah".
> I find myself doing that too many times, hence this shortcut.

I think I would find this handy. Shawn suggested an alias, but I think
this is perhaps "universal enough" to merit an actual command-line
option.

> +-m <message>::
> +--import=<message>::
> +
> +Commit everything to the newly initialized repository. This is equivalent to:

Maybe this is being too lazy, but I think it would be useful to allow
just "--import" without a message to default to "Initial commit",
"initial import from `basename $PWD`", or something like that.

> +		else if (!strcmp(arg, "--import") || !strcmp(arg, "-m")) {
> +			if (i+1 >= argc)
> +				die("--import requires an import message");
> +			import_message = argv[2];
> +			i++;
> +			argv++;
> +		}

I seem to recall that we were phasing out "--long-option <arg>" at some
point, and that all long-options should use "--long-option=". But maybe
I am mis-remembering.

-Peff

^ permalink raw reply

* GSoC proposal: git mirror-sync
From: Andrew Wang @ 2009-03-25  4:00 UTC (permalink / raw)
  To: git

Greetings,

I'm applying for a Google Summer of Code project for git mirror-sync,
mentioned on the Ideas wiki page. I've submitted my first draft to the
GSoC website already, but if any kind mailing list members have any
comments or questions I'd be happy to take them. Thanks go out to Sam
Vilian, who already helped me with my app :)

Cheers,
Andrew

==================================

Project Goals

What is the goal of your project?

The goal is to implement the MirrorSync protocol
(http://code.google.com/p/gittorrent/wiki/MirrorSync and
http://tinyurl.com/c7j3m7) as a continuation of previous Google Summer
of Code projects involving peer to peer distribution of Git
repositories to increase download speeds and decentralize
distribution. It's a refinement of the GitTorrent protocol
(http://gittorrent.utsl.gen.nz/rfc.html), which in turn was based on
the popular BitTorrent protocol
(http://www.bittorrent.org/beps/bep_0003.html). MirrorSync offers a
simplified version of GitTorrent more tailored to the design of Git
and without all the unnecessary BitTorrent cruft.

How would you measure its success or failure?

The MirrorSync overview goes over the three parts of functionality
that need to be implemented. Completing all three would be highly
desirable, but each part is useful in its own right since they add
mirror downloading functionality. However, the scope of the project
seems such that all three could be completed in a single Summer of
Code project, and that's what I'll aim for. There's going to be a
significant amount of work on formalizing the protocol and talking
with Git developers, and that'll also be part of my work for the
summer.

Part one is Mirror List. This lets a client hit a repository and get a
list of mirrors that the client could try downloading from, along with
the most recent update to the repository and the signing key for
verifying updates.

Part two is Mirror Notify. This is how a client can tell a repository
that the client has a copy of the repository, and is willing to act as
a mirror.

Part three is Mirror Sync. This is where the actual exchange of
repository contents occurs. The peers start by comparing each other's
latest packed-refs files, and then start requesting desired packs from
each other. This means new changes will quickly propagate through the
network, and since the objects are fragmented reproducibly, downloads
can be spread across many peers.

Describe your project in more detail.

The necessary steps to implementing all of MirrorSync have already
been laid out in skeleton form by Sam Vilain
(http://tinyurl.com/crdq9f). The three messages will be implemented
Mirror List first, followed by Mirror Notify and then Mirror Sync.
These are major milestones for progress, and more formal documentation
and specifications on each part should emerge along the way.

My plan for all three parts is to write the bulk of the code in
Python, since git-daemon can wrap the Python executables with
execl_git_cmd(). This means there are going to be additions to
git-daemon to accommodate this, but no big changes.

Mirror List requires modifications to git-tag to create the necessary
"push objects".These are tags containing a packed-refs file in the
comments section, signed so a mirror can verify the state of the
repository. The server has to maintain a list of mirrors and which
signing key IDs are allowed to push back to which branches. The client
has to make use of this key info to verify push objects, ensuring that
the push objects are only changing the allowed refs. Adding keys to
the keyring can be done through prompting similarly to how it's
handled in SSH. Finally, git-fetch will be modified to have a
"--use-mirror" flag to select from the list to download from a local
mirror.

Mirror Notify has to handle maintaining the list of mirrors and
responding appropriately. The main repository should attempt to verify
notify requests by checking that the repo exists on the mirror.
Writing the client commands for notifying the server should be pretty
easy.

Mirror Sync is probably the hardest to implement, since it's the most
complicated and requires the most investigation into Git internals. It
makes use of Mirror List to get a list of peers, and then it needs to
start advertising bundles it has and bundles it wants. Then the actual
downloading and uploading process happens, and this keeps happening
until everyone is in sync.

The timeline for the three months would look something like this: one
week of research  and planning, three weeks for Mirror List, two weeks
for Mirror Notify, five weeks for Mirror Sync, and one week for
cleanups and documentation. This is a total of 12 weeks, the time
allotted for Summer of Code.

Interfaces

What parts of Git will you need to call?

There won't be much code reuse, since the MirrorSync additions are
fairly separate from the rest of the Git codebase. The tag creation
facilities will be used in making "push objects", and the existing
framework in git-daemon for handling fetch requests will be extended
to handle the new MirrorSync requests.

What parts of Git might you need to change?

There are two things that will be changed: git-tag and git-daemon.
git-tag needs to be changed to accommodate creating the "push objects"
necessary for peer communication, and the required additions will
hopefully be minor. git-daemon needs to be modified to call out to the
new mirror-* functionality when it's hit with a request. Besides that,
three new commands are being added (mirror-list, mirror-notify,
mirror-sync) so the Makefile will need to be modified.

About You

Can you list some prior projects that you have worked on?

I haven't been involved in any open source projects much beyond bug
reporting and triaging, for KDE and a few other projects. I have a
couple very minor patches to Basket Note Pads in their Git tree, a KDE
project written in Qt and C++ (http://basket.kde.org/). As with most
open source projects, communication within Basket is primarily through
email, and I've also been subscribed on and off to development mailing
lists for KDE, Enlightenment, Gentoo, and now Git.

I've done plenty of coding on my own projects though, the most
relevant being Python implementations of a BitTorrent tracker and a
threaded XML feed grabber and parser. Both of these were done on my
own as self learning projects, using mainly the extensive standard
Python libraries. Python is my goto language for projects big and
small, and I'm pretty comfortable in it.

Besides that, my professional background has been in websites, so I
have exposure to all types of web development technologies and related
programming languages. My resume (http://www.linkedin.com/in/aawang)
is the best place to go for a run down on that part of my skill set.
I've also been producing copious amounts of Java code for school
related projects; right now we're doing development with Lego NXT
robots with communication over Bluetooth with a WiiRemote.

Do you have any prior Git experience? Have you started to get involved?

I don't have any prior Git experience except as a user. I asked Sam
Vilain a bit about MirrorSync when I saw the Git Ideas page, and now
I'm on the developer mailing list. I've also started browsing through
the source code, but I don't have any patches to my name right now.

^ permalink raw reply

* [PATCH v2] difftool: add various git-difftool tests
From: David Aguilar @ 2009-03-25  3:58 UTC (permalink / raw)
  To: gitster; +Cc: git, David Aguilar

t7800-difftool.sh tests the various command-line flags,
git-config variables, and environment settings supported by
git-difftool.

Signed-off-by: David Aguilar <davvid@gmail.com>
---
 t/t7800-difftool.sh |  139 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 139 insertions(+), 0 deletions(-)
 create mode 100755 t/t7800-difftool.sh

diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
new file mode 100755
index 0000000..ceef84b
--- /dev/null
+++ b/t/t7800-difftool.sh
@@ -0,0 +1,139 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 David Aguilar
+#
+
+test_description='git-difftool
+
+Testing basic diff tool invocation
+'
+
+. ./test-lib.sh
+
+remove_config_vars()
+{
+	# Unset all config variables used by git-difftool
+	git config --unset diff.tool
+	git config --unset difftool.test-tool.cmd
+	git config --unset merge.tool
+	git config --unset mergetool.test-tool.cmd
+	return 0
+}
+
+restore_test_defaults()
+{
+	# Restores the test defaults used by several tests
+	remove_config_vars
+	unset GIT_DIFF_TOOL
+	unset GIT_MERGE_TOOL
+	unset GIT_DIFFTOOL_NO_PROMPT
+	git config diff.tool test-tool &&
+	git config difftool.test-tool.cmd 'cat $LOCAL'
+}
+
+# Create a file on master and change it on branch
+test_expect_success 'setup' '
+	echo master >file &&
+	git add file &&
+	git commit -m "added file" &&
+
+	git checkout -b branch master &&
+	echo branch >file &&
+	git commit -a -m "branch changed file" &&
+	git checkout master
+'
+
+# Configure a custom difftool.<tool>.cmd and use it
+test_expect_success 'custom commands' '
+	restore_test_defaults &&
+	git config difftool.test-tool.cmd "cat \$REMOTE" &&
+
+	diff=$(git difftool --no-prompt branch) &&
+	test "$diff" = "master" &&
+
+	restore_test_defaults &&
+	diff=$(git difftool --no-prompt branch) &&
+	test "$diff" = "branch"
+'
+
+# Ensures that git-difftool ignores bogus --tool values
+test_expect_success 'difftool ignores bad --tool values' '
+	diff=$(git difftool --no-prompt --tool=bogus-tool branch)
+	test "$?" = 1 &&
+	test "$diff" = ""
+'
+
+# Specify the diff tool using $GIT_DIFF_TOOL
+test_expect_success 'GIT_DIFF_TOOL variable' '
+	git config --unset diff.tool
+	GIT_DIFF_TOOL=test-tool &&
+	export GIT_DIFF_TOOL &&
+
+	diff=$(git difftool --no-prompt branch) &&
+	test "$diff" = "branch" &&
+
+	restore_test_defaults
+'
+
+# Test the $GIT_*_TOOL variables and ensure
+# that $GIT_DIFF_TOOL always wins unless --tool is specified
+test_expect_success 'GIT_DIFF_TOOL overrides' '
+	git config diff.tool bogus-tool &&
+	git config merge.tool bogus-tool &&
+
+	GIT_MERGE_TOOL=test-tool &&
+	export GIT_MERGE_TOOL &&
+	diff=$(git difftool --no-prompt branch) &&
+	test "$diff" = "branch" &&
+	unset GIT_MERGE_TOOL &&
+
+	GIT_MERGE_TOOL=bogus-tool &&
+	GIT_DIFF_TOOL=test-tool &&
+	export GIT_MERGE_TOOL &&
+	export GIT_DIFF_TOOL &&
+
+	diff=$(git difftool --no-prompt branch) &&
+	test "$diff" = "branch" &&
+
+	GIT_DIFF_TOOL=bogus-tool &&
+	export GIT_DIFF_TOOL &&
+
+	diff=$(git difftool --no-prompt --tool=test-tool branch) &&
+	test "$diff" = "branch" &&
+
+	restore_test_defaults
+'
+
+# Test that we don't have to pass --no-prompt to difftool
+# when $GIT_DIFFTOOL_NO_PROMPT is true
+test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '
+	GIT_DIFFTOOL_NO_PROMPT=true &&
+	export GIT_DIFFTOOL_NO_PROMPT &&
+
+	diff=$(git difftool branch) &&
+	test "$diff" = "branch" &&
+
+	restore_test_defaults
+'
+
+# git-difftool falls back to git-mergetool config variables
+# so test that behavior here
+test_expect_success 'difftool + mergetool config variables' '
+	remove_config_vars
+	git config merge.tool test-tool &&
+	git config mergetool.test-tool.cmd "cat \$LOCAL" &&
+
+	diff=$(git difftool --no-prompt branch) &&
+	test "$diff" = "branch" &&
+
+	# set merge.tool to something bogus, diff.tool to test-tool
+	git config merge.tool bogus-tool &&
+	git config diff.tool test-tool &&
+
+	diff=$(git difftool --no-prompt branch) &&
+	test "$diff" = "branch" &&
+
+	restore_test_defaults
+'
+
+test_done
-- 
1.6.2.1.303.g63699

^ permalink raw reply related

* Re: [PATCH] difftool: add various git-difftool tests
From: David Aguilar @ 2009-03-25  3:57 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <7vr60m9vca.fsf@gitster.siamese.dyndns.org>

On Tue, Mar 24, 2009 at 7:42 PM, Junio C Hamano <gitster@pobox.com> wrote:
> David Aguilar <davvid@gmail.com> writes:
>
>> +remove_config_vars()
>> +{
>> +     # Unset all config variables used by git-difftool
>> +     git config --unset diff.tool
>> +     git config --unset difftool.test-tool.cmd
>> +     git config --unset merge.tool
>> +     git config --unset mergetool.test-tool.cmd
>> +     return 0
>> +}
>> +
>> +restore_test_defaults()
>> +{
>> +     # Restores the test defaults used by several tests
>> +     remove_config_vars
>> +     unset GIT_DIFF_TOOL &&
>> +     unset GIT_MERGE_TOOL &&
>> +     unset GIT_DIFFTOOL_NO_PROMPT &&
>
> I thought some shells' "unset" returns non-zero status when is given an
> already unset variable.  I suspect you would want to drop the && chain
> just like you did for remove_config_vars for the same reason.


Ah, I learn something new everyday.  Thanks.
I've addressed these notes and am sending patch v2 now.



>
>> +     git config diff.tool test-tool &&
>> +     git config difftool.test-tool.cmd "cat \$LOCAL"
>> +}
>
> 'cat $LOCAL' would be much easier to read, wouldn't it?
>
>> + ...
>> +# Specify the diff tool using $GIT_DIFF_TOOL
>> +test_expect_success 'GIT_DIFF_TOOL variable' '
>> +     git config --unset diff.tool &&
>
> You might want to lose && here in case the user told an earlier test that
> sets the configuration to some value skipped (or such test failed), or
> perhaps later somebody adds tests before this one that leaves the config
> without this variable.
>
> Other than that I didn't see major breakages.
>
> Thanks.
>



-- 
    David

^ permalink raw reply

* Re: [PATCH] init-db: support --import to add all files and commit right after init
From: Johannes Schindelin @ 2009-03-25  3:51 UTC (permalink / raw)
  To: Nguyễn Thái Ngọc Duy; +Cc: git
In-Reply-To: <1237946996-5287-1-git-send-email-pclouds@gmail.com>

[-- Attachment #1: Type: TEXT/PLAIN, Size: 551 bytes --]

Hi,

On Wed, 25 Mar 2009, Nguyễn Thái Ngọc Duy wrote:

> This is equivalent to "git init;git add .;git commit -q -m blah".
> I find myself doing that too many times, hence this shortcut.
> 
> In future, --fast-import support would also be nice if the import
> directory has a lot of files.
> 
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>

I wanted to have this for a _long_ time!

But could you please say "init" in the subject instead of "init-db"?  The 
latter is just a historical wart we have to carry around.

Thanks,
Dscho

^ permalink raw reply

* Re: branch ahead in commits but push claims all up to date
From: Daniel Barkalow @ 2009-03-25  3:19 UTC (permalink / raw)
  To: Irene Ros; +Cc: John Tapsell, git
In-Reply-To: <7001b7a00903241901w107e2973i9912eab114c9cde0@mail.gmail.com>

On Tue, 24 Mar 2009, Irene Ros wrote:

> Hi All,
> 
> Thank you for the good advice. I may be the case I am somehow misusing
> git... I couldn't resolve the issue and so I created a new project off
> of the same repo. Switching to the same branch in question yielded an
> even stranger result: In this new project, the commits were there (I
> could see them in git log and in git log origin/myBranch) whereas in
> the previous older project I did not... does that make sense? Our
> origin branches are located on a central server so can't quite figure
> out why viewing the log of the same remote branch from two different
> projects would yield different results. Any suggestions? At this
> point, I'm just really curious.

origin/* is a copy of what git saw the last time it talked to the remote 
repository. This may be different from what the remote repository now 
contains. (Also, there are a few cases in which pushing to a remote 
repository doesn't count as talking to it; fetching with a configured 
remote always counts.) The local copies are handy for being able to 
compare the work you've done locally with what is in the remote repository 
when you don't necessarily have a network connection, don't expect 
constant updates remotely, or don't want to be distracted by remote 
changes. For example, you might have your local work, and you might want 
to compare it with what other people have done. You want to avoid having 
additional changes that other people make while you're making this 
comparison show up in the middle.

Of course, when you make a new clone of the same repository, this clone 
will look at the repository when you make the clone, and will have the 
latest information (as of that time). 

In order to get an existing repository to see changes to a remote 
repository, use "git fetch <remote>" (you can leave off the <remote> to 
get the oone you gave to "clone", which is configured as "origin"). 
Alternatively, you can use "git pull" to get the data and also merge it in 
the same command, which may or may not be a useful addition depending on 
your workflow.

	-Daniel
*This .sig left intentionally blank*

^ permalink raw reply

* [PATCH 5/5] p4 example of git-vcs API for fetch direction
From: Daniel Barkalow @ 2009-03-25  3:04 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

---
 Documentation/git-vcs-p4.txt |   33 ++
 Makefile                     |   25 +
 p4-notes                     |   33 ++
 vcs-p4/p4client-api.cc       |  169 ++++++
 vcs-p4/p4client.c            |  137 +++++
 vcs-p4/p4client.h            |   38 ++
 vcs-p4/vcs-p4.c              | 1229 ++++++++++++++++++++++++++++++++++++++++++
 vcs-p4/vcs-p4.h              |  128 +++++
 8 files changed, 1792 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/git-vcs-p4.txt
 create mode 100644 p4-notes
 create mode 100644 vcs-p4/p4client-api.cc
 create mode 100644 vcs-p4/p4client.c
 create mode 100644 vcs-p4/p4client.h
 create mode 100644 vcs-p4/vcs-p4.c
 create mode 100644 vcs-p4/vcs-p4.h

diff --git a/Documentation/git-vcs-p4.txt b/Documentation/git-vcs-p4.txt
new file mode 100644
index 0000000..4039d24
--- /dev/null
+++ b/Documentation/git-vcs-p4.txt
@@ -0,0 +1,33 @@
+Config
+------
+
+vcs-p4.port::
+	The value to use for P4PORT
+
+vcs-p4.client::
+	The value to use for P4CLIENT
+
+vcs-p4.codelineformat::
+	A regular expression to match valid codelines; a codeline is a
+	directory that contains exactly those files that belong to a
+	version of a project. Importing history with integrations will
+	generally discover codelines not explicitly marked to be
+	imported, found when a file in a known codeline, whose full
+	path is therefore the codeline path plus a relative path, is
+	integrated from a file with a name that ends with that
+	relative path. However, files will sometimes be integrated
+	from non-codelines (that is, from a directory that contains
+	unrelated files whose history should not be tracked), and this
+	option can be used to ignore some directories.
+
+	Note that, properly, the history of the individual files from
+	a non-codeline which got integrated into a codeline should
+	contribute but that this is not presently supported.
+
+remotes.*.url::
+	The perforce location of a codeline to track. Other codelines
+	may be discovered by git-vcs-p4, but it will make no attempt
+	to get versions in these locations more recent than the last
+	versions that contribute at present to the tracked codelines,
+	and it will not make them available for matching in "fetch"
+	patterns.
diff --git a/Makefile b/Makefile
index 43a8364..b148b34 100644
--- a/Makefile
+++ b/Makefile
@@ -320,6 +320,7 @@ PROGRAMS += git-unpack-file$X
 PROGRAMS += git-update-server-info$X
 PROGRAMS += git-upload-pack$X
 PROGRAMS += git-var$X
+PROGRAMS += git-vcs-p4$X
 
 # List built-in command $C whose implementation cmd_$C() is not in
 # builtin-$C.o but is linked in as part of some other command.
@@ -1106,6 +1107,7 @@ endif
 ifneq ($(findstring $(MAKEFLAGS),s),s)
 ifndef V
 	QUIET_CC       = @echo '   ' CC $@;
+	QUIET_CXX      = @echo '   ' CXX $@;
 	QUIET_AR       = @echo '   ' AR $@;
 	QUIET_LINK     = @echo '   ' LINK $@;
 	QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
@@ -1288,12 +1290,16 @@ git.o git.spec \
 	$(patsubst %.perl,%,$(SCRIPT_PERL)) \
 	: GIT-VERSION-FILE
 
+vcs-p4/%.o: ALL_CFLAGS += -I.
+
 %.o: %.c GIT-CFLAGS
 	$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
 %.s: %.c GIT-CFLAGS
 	$(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $<
 %.o: %.S
 	$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
+%.o: %.cc GIT-CFLAGS
+	$(QUIET_CXX)$(CXX) -o $*.o -c $(ALL_CFLAGS) $<
 
 exec_cmd.o: exec_cmd.c GIT-CFLAGS
 	$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \
@@ -1329,6 +1335,24 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS)
 	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
 		$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
+P4API_BASE=/home/barkalow/stuff/p4api-2008.1.158777
+
+ifdef P4API_BASE
+P4_IMPL=p4client-api
+
+vcs-p4/p4client-api.o: ALL_CFLAGS += -I$(P4API_BASE)/include
+P4_LINK=$(CXX)
+P4LIBS=-L$(P4API_BASE)/lib -lclient -lrpc -lsupp
+else
+P4_IMPL=p4client
+P4_LINK=$(CC)
+endif
+
+git-vcs-p4$X: LIBS += $(P4LIBS)
+git-vcs-p4$X: vcs-p4/vcs-p4.o vcs-p4/$(P4_IMPL).o $(GITLIBS)
+	$(QUIET_LINK)$(P4_LINK) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
+		$(LIBS)
+
 $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
 $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)
 builtin-revert.o wt-status.o: wt-status.h
@@ -1583,6 +1607,7 @@ distclean: clean
 
 clean:
 	$(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
+		vcs-p4/*.o \
 		$(LIB_FILE) $(XDIFF_LIB)
 	$(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X
 	$(RM) $(TEST_PROGRAMS)
diff --git a/p4-notes b/p4-notes
new file mode 100644
index 0000000..bd95903
--- /dev/null
+++ b/p4-notes
@@ -0,0 +1,33 @@
+People using branches in p4 work like svn, except that the branches
+are not rooted at predictable places. Furthermore, there is not a
+uniform tree layout within a depot.
+
+Therefore, in order to generate a git repository from p4, it is
+necessary to specify a root within the depot as the working tree root
+in git. On the other hand, it should be possible to determine from the
+p4 history what portions of the depot outside of the root should be
+considered as branches, as it tracks "integrations".
+
+In theory, anyway, it should even be possible to produce a git
+repository with submodules when a similar thing has been done with
+integrations in p4, by determining that there are integrations into a
+subdirectory of the root.
+
+---
+
+Overview of operation:
+
+ - Allocate codeline
+ - Import codeline
+   - use p4_filelog to find the files and their revisions in the codeline
+   - For each file,
+
+---
+Saving processed state
+
+ - Record for each codeline
+   - What are all the changesets?
+
+ - Record for each codeline/changeset
+   - What's the commit
+
diff --git a/vcs-p4/p4client-api.cc b/vcs-p4/p4client-api.cc
new file mode 100644
index 0000000..df8b606
--- /dev/null
+++ b/vcs-p4/p4client-api.cc
@@ -0,0 +1,169 @@
+extern "C" {
+#include "p4client.h"
+}
+
+#include <p4/clientapi.h>
+
+class VCSClientUser : public ClientUser {
+  virtual void OutputInfo(char level, const char *data);
+  virtual void OutputBinary(const char *data, int length);
+  virtual void OutputText(const char *data, int length);
+public:
+  void *data;
+  void (*info_cb)(void *, int, const char *);
+  void (*form_cb)(void *, const char *, const char *);
+
+  void (*buffer_cb)(void *, const char *buffer, int length);
+
+  void clear() {
+    info_cb = NULL;
+    form_cb = NULL;
+    buffer_cb = NULL;
+  }
+};
+
+static ClientApi client;
+static VCSClientUser ui;
+
+void p4_init(const char *const *env)
+{
+  Error e;
+  StrBuf msg;
+
+  while (*env) {
+    if (!strncmp(*env, "P4PORT=", 7))
+      client.SetPort((*env) + 7);
+    env++;
+  }
+
+  client.Init(&e);
+  if (e.Test()) {
+    e.Fmt(&msg);
+    fprintf(stderr, msg.Text());
+    exit(1);
+  }
+}
+
+void VCSClientUser::OutputBinary(const char *buffer, int length)
+{
+  if (buffer_cb) {
+    buffer_cb(data, buffer, length);
+  } else
+    fprintf(stderr, "Unexpected binary of length %d\n", length);
+}
+
+void VCSClientUser::OutputText(const char *buffer, int length)
+{
+  if (buffer_cb) {
+    buffer_cb(data, buffer, length);
+  } else
+    fprintf(stderr, "Unexpected text of length %d\n", length);
+}
+
+void VCSClientUser::OutputInfo(char level, const char *line)
+{
+  if (info_cb)
+    info_cb(data, level - '0', line);
+  else if (form_cb) {
+    struct strbuf key;
+    struct strbuf value;
+
+    strbuf_init(&key, 0);
+    strbuf_init(&value, 0);
+
+    const char *eol = NULL;
+    for (; *line; line = eol + 1) {
+      const char *eok;
+
+      eol = strchr(line, '\n');
+      if (!eol)
+	break;
+      if (eol == line || line[0] == '#')
+	continue;
+      eok = strchr(line, ':');
+      if (!eok)
+	continue;
+      strbuf_reset(&key);
+      strbuf_reset(&value);
+      strbuf_add(&key, line, eok - line);
+      if (eok[1] == '\t') {
+	strbuf_add(&value, eok + 2, eol - (eok + 2));
+      } else if (eok[1] == '\n') {
+	for (line = eol + 1; *line && line[0] != '\n'; line = eol + 1) {
+	  eol = strchr(line, '\n');
+	  strbuf_add(&value, line + 1, eol - (line + 1) + 1);
+	}
+      }
+      form_cb(data, key.buf, value.buf);
+    }
+    strbuf_release(&key);
+    strbuf_release(&value);
+  } else
+    fprintf(stderr, "Unexpected info: %c ... %s\n", level, line);
+}
+
+int p4_call(int fds[], const char *arg0, int argc, char *const *argv)
+{
+  ui.data = NULL;
+  ui.clear();
+  client.SetArgv(argc, argv);
+  client.Run(arg0, &ui);
+  p4_fini();
+  exit(1);
+  return 0;
+}
+
+int _p4_call_info(const char *arg0, int argc, char *const *argv,
+		  void *data,
+		  void (*cb)(void *data, int level, const char *line))
+{
+  ui.clear();
+  ui.data = data;
+  ui.info_cb = cb;
+  client.SetArgv(argc, argv);
+  client.Run(arg0, &ui);
+  return 0;
+}
+
+int _p4_call_form(const char *arg0, int argc, char *const *argv,
+		  void *data,
+		  void (*cb)(void *data, const char *key, const char *value))
+{
+  ui.clear();
+  ui.data = data;
+  ui.form_cb = cb;
+  client.SetArgv(argc, argv);
+  client.Run(arg0, &ui);
+  return 0;
+}
+
+int _p4_call_buffer(const char *arg0, int argc, char *const *argv,
+		    void *data,
+		    void (*cb)(void *data, const char *buffer, int length))
+{
+  ui.clear();
+  ui.data = data;
+  ui.buffer_cb = cb;
+  client.SetArgv(argc, argv);
+  client.Run(arg0, &ui);
+  return 0;
+}
+
+int p4_complete()
+{
+  return 0;
+}
+
+int p4_fini()
+{
+  Error e;
+  StrBuf msg;
+
+  client.Final(&e);
+  if (e.Test()) {
+    e.Fmt(&msg);
+    fprintf(stderr, msg.Text());
+    exit(1);
+  }
+  return 0;
+}
diff --git a/vcs-p4/p4client.c b/vcs-p4/p4client.c
new file mode 100644
index 0000000..96ccdf9
--- /dev/null
+++ b/vcs-p4/p4client.c
@@ -0,0 +1,137 @@
+#include "p4client.h"
+
+#include "cache.h"
+#include "run-command.h"
+
+static const char *const *envp;
+
+void p4_init(const char *const *env)
+{
+	envp = env;
+}
+
+static struct child_process child;
+
+int p4_call(int fds[], const char *arg0, int argc, char *const *argv)
+{
+	int i;
+	memset(&child, 0, sizeof(child));
+	if (fds) {
+		child.in = -1;
+		child.out = -1;
+	} else {
+		child.no_stdin = 1;
+		child.no_stdout = 1;
+	}
+	child.err = 0;
+	child.argv = xcalloc(argc + 3, sizeof(*argv));
+	child.argv[0] = "p4";
+	child.argv[1] = arg0;
+	child.env = envp;
+	for (i = 0; i < argc; i++)
+		child.argv[i + 2] = argv[i];
+	child.argv[argc + 2] = NULL;
+	start_command(&child);
+	if (fds) {
+		fds[0] = child.in;
+		fds[1] = child.out;
+	}
+	return 0;
+}
+
+int _p4_call_info(const char *arg0, int argc, char *const *argv, void *data,
+		  void (*cb)(void *data, int level, const char *line))
+{
+	int fds[2];
+	struct strbuf line;
+	FILE *input;
+
+	if (p4_call(fds, arg0, argc, argv))
+		return -1;
+
+	strbuf_init(&line, 0);
+	input = fdopen(fds[1], "r");
+	while (!strbuf_getline(&line, input, '\n')) {
+		int level = 0;
+		char *posn = line.buf;
+		while (!prefixcmp(posn, "... ")) {
+			posn += 4;
+			level++;
+		}
+		cb(data, level, posn);
+	}
+	p4_complete();
+	return 0;
+}
+
+int _p4_call_form(const char *arg0, int argc, char *const *argv, void *data,
+		  void (*cb)(void *data, const char *key, const char *value))
+{
+	int fds[2];
+	struct strbuf line;
+	struct strbuf key;
+	struct strbuf value;
+	FILE *input;
+
+	if (p4_call(fds, arg0, argc, argv))
+		return -1;
+
+	strbuf_init(&line, 0);
+	strbuf_init(&key, 0);
+	strbuf_init(&value, 0);
+	input = fdopen(fds[1], "r");
+	for (; !strbuf_getline(&line, input, '\n'); strbuf_reset(&line)) {
+		const char *eok;
+
+		if (!line.buf[0] || line.buf[0] == '#')
+			continue;
+		eok = strchr(line.buf, ':');
+		if (!eok)
+			continue;
+		strbuf_reset(&key);
+		strbuf_reset(&value);
+		strbuf_add(&key, line.buf, eok - line.buf);
+		if (eok[1] == '\t')
+			strbuf_addstr(&value, eok + 2);
+		else {
+			strbuf_reset(&line);
+			while (!strbuf_getline(&line, input, '\n') && line.len) {
+				strbuf_addstr(&value, line.buf + 1);
+				strbuf_addch(&value, '\n');
+				strbuf_reset(&line);
+			}
+		}
+		cb(data, key.buf, value.buf);
+	}
+	p4_complete();
+	return 0;
+}
+
+int _p4_call_buffer(const char *arg0, int argc, char *const *argv, void *data,
+		    void (*cb)(void *data, const char *buffer, int len))
+{
+	int fds[2];
+	struct strbuf block;
+	if (p4_call(fds, arg0, argc, argv))
+		return -1;
+	strbuf_init(&block, 0);
+	strbuf_read(&block, fds[1], 0);
+	cb(data, block.buf, block.len);
+	p4_complete();
+	return 0;
+}
+
+int p4_complete(void)
+{
+	if (!child.no_stdin)
+		close(child.in);
+	if (!child.no_stdout)
+		close(child.out);
+	finish_command(&child);
+	return 0;
+}
+
+int p4_fini(void)
+{
+	return 0;
+}
diff --git a/vcs-p4/p4client.h b/vcs-p4/p4client.h
new file mode 100644
index 0000000..d0e4ccd
--- /dev/null
+++ b/vcs-p4/p4client.h
@@ -0,0 +1,38 @@
+#ifndef P4CLIENT_H
+#define P4CLIENT_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "strbuf.h"
+
+void p4_init(const char *const *env);
+
+int p4_call(int fds[], const char *arg0, int argc, char *const *argv);
+
+#define p4_call_info(arg0, argc, argv, data, cb) \
+	(0 ? ((*(cb))((data), 0, NULL), 1) : \
+	 _p4_call_info(arg0, argc, argv, (void *)data, (void (*)(void *, int, const char *)) cb))
+
+#define p4_call_form(arg0, argc, argv, data, cb) \
+	(0 ? ((*(cb))((data), NULL, NULL), 1) : \
+	 _p4_call_form(arg0, argc, argv, (void *)data, (void (*)(void *, const char *, const char *)) cb))
+
+#define p4_call_buffer(arg0, argc, argv, data, cb) \
+	(0 ?  ((*(cb))((data), NULL, 0), 1) : \
+	 _p4_call_buffer(arg0, argc, argv, (void *)data, (void (*)(void *, const char *, int)) cb))
+
+int p4_complete();
+
+int p4_fini();
+
+int _p4_call_info(const char *arg0, int argc, char *const *argv, void *data,
+		  void (*cb)(void *data, int level, const char *line));
+
+int _p4_call_form(const char *arg0, int argc, char *const *argv, void *data,
+		  void (*cb)(void *data, const char *key, const char *value));
+
+int _p4_call_buffer(const char *arg0, int argc, char *const *argv, void *data,
+		    void (*cb)(void *data, const char *buffer, int len));
+
+#endif
diff --git a/vcs-p4/vcs-p4.c b/vcs-p4/vcs-p4.c
new file mode 100644
index 0000000..36f7d3f
--- /dev/null
+++ b/vcs-p4/vcs-p4.c
@@ -0,0 +1,1229 @@
+#include "cache.h"
+#include "vcs-p4.h"
+#include "strbuf.h"
+#include "remote.h"
+#include "commit.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "diff.h"
+
+#include "p4client.h"
+
+#include <string.h>
+
+/** Should we try to find codelines that branch off of the relevant
+ * ones, for future reference? This lets us find new things in
+ * ls-remote without making the user tell us.
+ **/
+static int find_new_codelines;
+
+static int ignore_codeline_nr;
+static int ignore_codeline_alloc;
+static char **ignore_codelines;
+
+static int prints_done = 0;
+
+static regex_t *codeline_regex;
+
+#define CODELINE_TAG "Codeline: "
+#define CHANGE_TAG "Changelist: "
+
+#define LIST_P4_OPERATIONS 0
+
+/** List functions **/
+
+static void add_to_revision_list(struct p4_revision_list **list,
+				 struct p4_revision *revision)
+{
+	while (*list)
+		list = &(*list)->next;
+	*list = xcalloc(1, sizeof(**list));
+	(*list)->revision = revision;
+}
+
+static struct p4_revision_list *copy_revision_list(struct p4_revision_list *lst)
+{
+	struct p4_revision_list *ret, **posn = &ret;
+	while (lst) {
+		*posn = xcalloc(1, sizeof(**posn));
+		(*posn)->revision = lst->revision;
+		posn = &((*posn)->next);
+		lst = lst->next;
+	}
+	return ret;
+}
+
+/** Functions to find or create representations **/
+
+static struct p4_depot *get_depot(void)
+{
+	struct p4_depot *depot = xcalloc(1, sizeof(*depot));
+	depot->next_mark = 1;
+	return depot;
+}
+
+static struct p4_changeset *get_changeset(struct p4_codeline *codeline,
+					  long number);
+
+static char *codeline_to_refname(const char *path) {
+	struct strbuf buf;
+	if (prefixcmp(path, "//"))
+		return NULL;
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "refs/p4/%s", path + 2);
+	return strbuf_detach(&buf, NULL);
+}
+
+static char *refname_to_codeline(const char *refname) {
+	struct strbuf buf;
+	if (prefixcmp(refname, "refs/p4/"))
+		return NULL;
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "//%s", refname + strlen("refs/p4/"));
+	return strbuf_detach(&buf, NULL);
+}
+
+static struct p4_codeline *get_codeline(struct p4_depot *depot, const char *path)
+{
+	struct p4_codeline **posn, *codeline;
+	int i;
+	unsigned char sha1[20];
+
+	if (codeline_regex && regexec(codeline_regex, path, 0, NULL, 0))
+		return NULL;
+
+	for (posn = &depot->codelines; *posn; posn = &(*posn)->next)
+		if (!strcmp(path, (*posn)->path))
+			return *posn;
+	codeline = xcalloc(1, sizeof(*codeline));
+	codeline->depot = depot;
+	codeline->path = xstrdup(path);
+
+	for (i = 0; i < ignore_codeline_nr; i++)
+		if (!strcmp(path, ignore_codelines[i]))
+			codeline->ignore = 1;
+
+	codeline->refname = codeline_to_refname(path);
+	if (!get_sha1(codeline->refname, sha1)) {
+		struct commit *commit = lookup_commit(sha1);
+		char *field;
+		parse_commit(commit);
+		field = strstr(commit->buffer, CHANGE_TAG);
+		if (!field) {
+			fprintf(stderr, "Couldn't find changeset line in commit\n");
+		} else {
+			struct p4_changeset *changeset;
+			codeline->finished_changeset =
+				atoi(field + strlen(CHANGE_TAG));
+			changeset = get_changeset(codeline, codeline->finished_changeset);
+			changeset->commit = commit;
+			codeline->history = changeset;
+		}
+	}
+	*posn = codeline;
+	return codeline;
+}
+
+static struct p4_codeline *find_codeline(struct p4_depot *depot, const char *path)
+{
+	struct p4_codeline **posn;
+	for (posn = &depot->codelines; *posn; posn = &(*posn)->next)
+		if (!prefixcmp(path, (*posn)->path))
+			return *posn;
+	return NULL;
+}
+
+/** Inserts the changeset at the right place in order for the codeline **/
+static struct p4_changeset *get_changeset(struct p4_codeline *codeline,
+					  long number)
+{
+	struct p4_changeset **posn = &codeline->changesets;
+	struct p4_changeset *changeset, *prev = NULL;
+	while (*posn && (*posn)->number < number) {
+		prev = *posn;
+		posn = &(*posn)->next;
+	}
+	if (*posn && (*posn)->number == number)
+		return *posn;
+	//printf("# add changeset %lu in %s\n", number, codeline->path);
+	changeset = xcalloc(1, sizeof(*changeset));
+	changeset->codeline = codeline;
+	changeset->next = *posn;
+	changeset->previous = prev;
+	if (changeset->next)
+		changeset->next->previous = changeset;
+	else
+		codeline->head = changeset;
+	*posn = changeset;
+	changeset->number = number;
+	codeline->num_changesets++;
+	return changeset;
+}
+
+static struct p4_changeset *changeset_from_commit(struct p4_depot *depot,
+						  struct commit *commit)
+{
+	unsigned long number = 0;
+	char *codeline = NULL, *field;
+	parse_commit(commit);
+	field = strstr(commit->buffer, CHANGE_TAG);
+	if (field)
+		number = atoi(field + strlen(CHANGE_TAG));
+	field = strstr(commit->buffer, CODELINE_TAG);
+	if (field) {
+		char *end;
+		codeline = field + strlen(CODELINE_TAG);
+		end = strchr(codeline, '\n');
+		if (end)
+			*end = '\0';
+	}
+	if (number && codeline)
+		return get_changeset(get_codeline(depot, codeline), number);
+	return NULL;
+}
+
+static struct p4_file *get_file_by_full(struct p4_codeline *codeline,
+					const char *fullpath)
+{
+	const char *rel = fullpath + strlen(codeline->path);
+	struct p4_file **posn;
+	for (posn = &codeline->files; *posn; posn = &(*posn)->next) {
+		if (!strcmp((*posn)->name, rel))
+			return *posn;
+	}
+	*posn = xcalloc(1, sizeof(**posn));
+	(*posn)->codeline = codeline;
+	(*posn)->name = xstrdup(rel);
+	return *posn;
+}
+
+static struct p4_file *get_related_file(struct p4_file *base, const char *path)
+{
+	int basenamelen = strlen(base->name);
+	int reldirlen = strlen(path) - basenamelen;
+	struct p4_codeline *codeline;
+	if (reldirlen > 0 && !strcmp(path + reldirlen, base->name)) {
+		/* File with the same name in another codeline */
+		char *other = xstrndup(path, reldirlen);
+		//printf("# find %s in %s\n", path, other);
+		codeline = get_codeline(base->codeline->depot, other);
+		free(other);
+		if (codeline)
+			return get_file_by_full(codeline, path);
+		return NULL;
+	}
+	codeline = find_codeline(base->codeline->depot, path);
+	if (codeline) {
+		/* File with a different name in some known codeline */
+		return get_file_by_full(codeline, path);
+	}
+	fprintf(stderr, "Trying to identify %s\n", path);
+	/* Not in any known codeline; need to recheck this after
+	 * discovering codelines completes.
+	 */
+	return NULL;
+}
+
+static struct p4_revision *get_revision(struct p4_file *file, unsigned number)
+{
+	struct p4_revision **posn;
+	struct p4_revision *revision;
+	for (posn = &file->revisions; *posn && (*posn)->number < number;
+	     posn = &(*posn)->next)
+		;
+	if (!*posn || (*posn)->number != number) {
+		revision = xcalloc(1, sizeof(*revision));
+		revision->next = *posn;
+		*posn = revision;
+		revision->number = number;
+		revision->file = file;
+	}
+	return *posn;
+}
+
+static int parse_p4_date(const char *date)
+{
+	struct tm tm;
+	memset(&tm, 0, sizeof(tm));
+	tm.tm_year = strtol(date, NULL, 10) - 1900;
+	tm.tm_mon = strtol(date + 5, NULL, 10) - 1;
+	tm.tm_mday = strtol(date + 8, NULL, 10);
+	tm.tm_hour = strtol(date + 11, NULL, 10);
+	tm.tm_min = strtol(date + 14, NULL, 10);
+	tm.tm_sec = strtol(date + 17, NULL, 10);
+	return mktime(&tm);
+}
+
+static int is_keyword(const char *text, int keywords)
+{
+	if (!prefixcmp(text, "Id: ") ||
+	    !prefixcmp(text, "Header: "))
+		return 1;
+	if (keywords == 1)
+		return 0;
+	return !prefixcmp(text, "Date: ") ||
+		!prefixcmp(text, "DateTime: ") ||
+		!prefixcmp(text, "Change: ") ||
+		!prefixcmp(text, "File: ") ||
+		!prefixcmp(text, "Revision: ") ||
+		!prefixcmp(text, "Author: ");
+}
+
+static void handle_keywords(struct strbuf *buf, int keywords)
+{
+	int posn = 0;
+	char *keyword;
+
+	if (!keywords)
+		return;
+
+	do {
+		keyword = strchr(buf->buf + posn, '$');
+		if (!keyword)
+			break;
+		if (!is_keyword(keyword + 1, keywords)) {
+			posn = keyword - buf->buf + 1;
+			continue;
+		}
+		char *eok = strchr(keyword + 1, ':');
+		size_t kwl = strcspn(eok, "$\n");
+		if (!eok[kwl])
+			break;
+		if (eok[kwl] == '$') {
+			strbuf_remove(buf, eok - buf->buf, kwl);
+		} else {
+			posn = eok - buf->buf + kwl + 1;
+		}
+	} while (1);
+}
+
+static const char *get_file_type(const char *text, size_t len)
+{
+	if (len == 5 && !memcmp(text, "ktext", 5))
+		return "text+k";
+	if (len == 5 && !memcmp(text, "xtext", 5))
+		return "text+x";
+	if (len == 6 && !memcmp(text, "kxtext", 6))
+		return "text+kx";
+	return xstrndup(text, len);
+}
+
+static const char *get_file_mode(const char *type)
+{
+	char *p = strchr(type, '+');
+	if (!strcmp(type, "symlink"))
+		return "120000";
+	if (p && strchr(p, 'x'))
+		return "100755";
+	return "100644";
+}
+
+static int keywords(const char *type)
+{
+	char *p = strchr(type, '+');
+	if (p) {
+		char *k = strchr(p, 'k');
+		if (k) {
+			if (k[1] == 'o')
+				return 1;
+			return 2;
+		}
+	}
+	return 0;
+}
+
+static void output_data(struct strbuf *buf)
+{
+	printf("data %d\n", buf->len);
+	fwrite(buf->buf, 1, buf->len, stdout);
+	printf("\n");
+}
+
+static int write_blob(struct p4_codeline *codeline,
+		      const unsigned char *sha1,
+		      const char *path)
+{
+	struct strbuf buf;
+	void *content;
+	enum object_type type;
+	unsigned long size;
+	int fd;
+
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "%s/%s", codeline->working, path);
+	content = read_sha1_file(sha1, &type, &size);
+	fd = open(buf.buf, O_WRONLY | O_CREAT, 0666);
+	if (fd < 0) {
+		die("Got err %d", errno);
+	}
+	write_or_die(fd, content, size);
+	return 0;
+}
+
+/** P4 operations **/
+
+static int p4_where(struct p4_codeline *codeline)
+{
+	int fds[2];
+	char *argv[1];
+	struct strbuf buf;
+
+	strbuf_init(&buf, 0);
+	strbuf_addstr(&buf, codeline->path);
+	argv[0] = buf.buf;
+	p4_call(fds, "where", 1, argv);
+	FILE *input = fdopen(fds[1], "r");
+
+	while (!strbuf_getline(&buf, input, '\n')) {
+		char *working = strrchr(buf.buf, ' ');
+		if (working)
+			codeline->working = xstrdup(working + 1);
+	}
+	p4_complete();
+	return codeline->working ? 0 : -1;
+}
+
+static void p4_sync(struct p4_codeline *codeline)
+{
+	char *argv[1];
+	struct strbuf buf;
+
+	printf("progress syncing %s/...\n", codeline->working);
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "%s/...@%lu",
+		    codeline->working, codeline->head->number);
+	argv[0] = buf.buf;
+	p4_call(NULL, "sync", 1, argv);
+	p4_complete();
+}
+
+static void p4_edit(struct p4_codeline *codeline, const char *path)
+{
+	char *argv[1];
+	struct strbuf buf;
+
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "%s/%s", codeline->working, path);
+	argv[0] = buf.buf;
+	p4_call(NULL, "edit", 1, argv);
+	strbuf_release(&buf);
+	p4_complete();
+}
+
+static void p4_add(struct p4_codeline *codeline, const char *path)
+{
+	char *argv[1];
+	struct strbuf buf;
+
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "%s/%s", codeline->working, path);
+	argv[0] = buf.buf;
+	p4_call(NULL, "add", 1, argv);
+	strbuf_release(&buf);
+	p4_complete();
+}
+
+static void p4_delete(struct p4_codeline *codeline, const char *path)
+{
+	char *argv[1];
+	struct strbuf buf;
+
+	strbuf_init(&buf, 0);
+	strbuf_addf(&buf, "%s/%s", codeline->working, path);
+	argv[0] = buf.buf;
+	p4_call(NULL, "delete", 1, argv);
+	strbuf_release(&buf);
+	p4_complete();
+}
+
+static void p4_submit(struct commit *commit)
+{
+	int fds[2];
+	char *argv[1];
+	int skip = 0;
+	argv[0] = "-o";
+	p4_call(fds, "change", 1, argv);
+
+	struct strbuf message;
+	struct strbuf line;
+
+	FILE *input = fdopen(fds[1], "r");
+
+	strbuf_init(&message, 0);
+	strbuf_init(&line, 0);
+
+	while (!strbuf_getline(&line, input, '\n')) {
+		if (!skip) {
+			strbuf_addstr(&message, line.buf);
+			strbuf_addch(&message, '\n');
+		}
+		if (line.buf[0] != '\t')
+			skip = 0;
+		if (!strcmp(line.buf, "Description:")) {
+			char *posn;
+			parse_commit(commit);
+			posn = strstr(commit->buffer, "\n\n");
+			if (posn)
+				posn += 2;
+			while (*posn) {
+				char *eol = strchr(posn, '\n');
+				strbuf_addstr(&message, "\t");
+				if (eol) {
+					eol++;
+					strbuf_add(&message, posn, eol - posn);
+					posn = eol;
+				} else {
+					strbuf_addstr(&message, posn);
+					break;
+				}
+			}
+			strbuf_addstr(&message, "\n");
+			skip = 1;
+		}
+	}
+
+	fclose(input);
+	p4_complete();
+
+	printf("%s\n", message.buf);
+
+	argv[0] = "-i";
+	p4_call(fds, "submit", 1, argv);
+
+	write_or_die(fds[0], message.buf, message.len);
+	close(fds[0]);
+
+	input = fdopen(fds[1], "r");
+	while (!strbuf_getline(&line, input, '\n'))
+		fprintf(stderr, "%s\n", line.buf);
+	p4_complete();
+}
+
+static void p4_print(struct p4_revision *revision)
+{
+	char *argv[2];
+	struct strbuf line;
+	strbuf_init(&line, 0);
+	strbuf_addf(&line, "%s%s#%lu",
+		    revision->file->codeline->path,
+		    revision->file->name, revision->number);
+	argv[1] = strdup(line.buf);
+	argv[0] = "-q";
+
+	if (LIST_P4_OPERATIONS)
+		fprintf(stderr, "p4 print\n");
+
+	strbuf_reset(&line);
+
+	p4_call_buffer("print", 2, argv, &line, strbuf_add);
+
+	free(argv[1]);
+
+	handle_keywords(&line, keywords(revision->type));
+
+	/* Perforce puts a newline at the end when printing symlinks */
+	if (!strcmp(revision->type, "symlink"))
+		line.len--;
+
+	output_data(&line);
+
+	strbuf_release(&line);
+
+	prints_done++;
+}
+
+struct p4_change_data {
+	struct p4_changeset *changeset;
+	int date;
+	char *user;
+	struct strbuf message;
+};
+
+static void p4_change_cb(struct p4_change_data *data, const char *key,
+			 const char *value)
+{
+	if (!strcmp(key, "User"))
+		data->user = xstrdup(value);
+	else if (!strcmp(key, "Date"))
+		data->date = parse_p4_date(value);
+	else if (!strcmp(key, "Description"))
+		strbuf_addstr(&data->message, value);
+}
+
+static void p4_change(struct p4_changeset *changeset)
+{
+	char *argv[2];
+	struct strbuf line;
+
+	struct p4_change_data data = {
+		.changeset = changeset,
+		.date = 0,
+		.user = NULL,
+	};
+
+	if (LIST_P4_OPERATIONS)
+		fprintf(stderr, "p4 change\n");
+
+	strbuf_init(&data.message, 0);
+
+	strbuf_init(&line, 0);
+	strbuf_addf(&line, "%lu", changeset->number);
+	argv[1] = line.buf;
+	argv[0] = "-o";
+	p4_call_form("change", 2, argv, &data, p4_change_cb);
+	strbuf_release(&line);
+
+	printf("committer %s <%s> %d +0000\n",
+	       data.user, data.user, data.date);
+	free(data.user);
+
+	strbuf_addf(&data.message,
+		    "\n" CODELINE_TAG "%s\n" CHANGE_TAG "%lu\n",
+		    changeset->codeline->path, changeset->number);
+	output_data(&data.message);
+	strbuf_release(&data.message);
+}
+
+struct p4_filelog_data {
+	struct p4_codeline *codeline;
+	struct p4_file *file;
+	struct p4_revision *revision;
+};
+
+static void p4_filelog_cb(struct p4_filelog_data *data,
+			  char level, const char *line)
+{
+	if (level == 0) {
+		data->file = get_file_by_full(data->codeline, line);
+	} else if (level == 1) {
+		int rev, change, delete = 0, branch = 0;
+		char *posn;
+		rev = strtoul(line + 1, &posn, 10);  /* skip the '#' */
+		posn += strlen(" change ");
+		change = strtoul(posn, &posn, 10);
+		if (!prefixcmp(posn, " delete"))
+			delete = 1;
+		if (!prefixcmp(posn, " branch"))
+			branch = 1;
+		posn = strchr(posn, '(') + 1;
+		data->revision = get_revision(data->file, rev);
+		data->revision->changeset =
+			get_changeset(data->codeline, change);
+		data->revision->type = get_file_type(posn,
+						     strchr(posn, ')') - posn);
+		data->revision->delete = delete;
+		data->revision->branch = branch;
+		add_to_revision_list(&data->revision->changeset->revisions,
+				     data->revision);
+	} else if (level == 2) {
+		const char *path;
+		int rev, from = 0;
+		char *type = xstrdup(line);
+		char *posn = strrchr(type, ' ') + 1;
+
+		from = (!prefixcmp(type, "ignored") &&
+			posn == type + strlen("ignored") + 1) ||
+			!prefixcmp(strchr(type, ' '), " from");
+
+		path = posn;
+		posn = strchr(posn, '#');
+		*(posn++) = '\0';
+		do {
+			/* ???? What does a list of revisions mean? */
+			rev = strtoul(posn, &posn, 10);
+			if (*posn != ',')
+				break;
+			posn += 2;
+		} while (1);
+		if (from) {
+			struct p4_file *rel_file =
+				get_related_file(data->file, path);
+			if (!rel_file) {
+				/*
+				printf("# Couldn't find %s related to %s %s\n",
+				       path, data->file->codeline->path,
+				       data->file->name);
+				*/
+			}
+			if (rel_file && rel_file->codeline != data->codeline)
+				add_to_revision_list(&data->revision->integrated,
+						     get_revision(rel_file, rev));
+		} else if (find_new_codelines) {
+			/* This is an "<op> into <path>#<rev>" line.
+			 * We just want to try to create a codeline.
+			 */
+			get_related_file(data->file, path);
+		}
+		free(type);
+	}
+}
+
+/** Finds all files in the codeline, and all revisions of those files,
+ * and all of the changesets they are from, and looks up the codelines
+ * and files they integrate or branch.
+ **/
+static void p4_filelog(struct p4_codeline *codeline)
+{
+	struct strbuf line;
+
+	struct p4_filelog_data data = {
+		.codeline = codeline,
+		.file = NULL,
+		.revision = NULL
+	};
+	char *arg;
+
+	if (codeline->filelog_done)
+		return;
+
+	if (LIST_P4_OPERATIONS)
+		fprintf(stderr, "p4 filelog %s\n", codeline->path);
+
+	strbuf_init(&line, 0);
+	strbuf_addstr(&line, codeline->path);
+	strbuf_addstr(&line, "/...");
+	arg = line.buf;
+	p4_call_info("filelog", 1, &arg, &data, p4_filelog_cb);
+	strbuf_release(&line);
+	if (codeline->history)
+		codeline->unreported = codeline->history->next;
+	else
+		codeline->unreported = codeline->changesets;
+	codeline->filelog_done = 1;
+}
+
+/** Functions to import things (i.e., fill out the representations) **/
+
+static struct p4_changeset_list *
+find_codeline_changeset(struct p4_changeset_list **list,
+			struct p4_codeline *codeline)
+{
+	while (*list) {
+		if ((*list)->changeset->codeline == codeline)
+			return *list;
+		list = &(*list)->next;
+	}
+	*list = xcalloc(1, sizeof(**list));
+	return *list;
+}
+
+static void resolve_codeline_contents(struct p4_codeline *codeline)
+{
+	struct p4_revision_list *prevrevs = NULL;
+	struct p4_changeset *changeset = codeline->changesets;
+	while (changeset) {
+		struct p4_revision_list *changes =
+			copy_revision_list(changeset->revisions);
+		changeset->contents = changes;
+		while (prevrevs) {
+			struct p4_revision_list *posn;
+			int found = 0;
+			for (posn = changes; posn; posn = posn->next) {
+				if (prevrevs->revision->file ==
+				    posn->revision->file) {
+					found = 1;
+					break;
+				}
+			}
+			if (!found) {
+				struct p4_revision_list *item =
+					xcalloc(1, sizeof(*item));
+				item->revision = prevrevs->revision;
+				item->next = changeset->contents;
+				changeset->contents = item;
+			}
+			prevrevs = prevrevs->next;
+		}
+
+		prevrevs = changeset->contents;
+		changeset = changeset->next;
+	}
+}
+
+static void resolve_changeset_integrates(struct p4_changeset *changeset)
+{
+	struct p4_revision_list *posn;
+	struct p4_changeset_list *changesets = NULL;
+	/* For each codeline, we want the highest numbered changeset
+	 * that introduced a revision that has been integrated.
+	 */
+	for (posn = changeset->revisions; posn; posn = posn->next) {
+		struct p4_revision_list *rev_ints = posn->revision->integrated;
+		while (rev_ints) {
+			struct p4_changeset_list *item;
+			if (rev_ints->revision->file->codeline == changeset->codeline) {
+				rev_ints = rev_ints->next;
+				continue;
+			}
+			/* The revision doesn't have the changeset
+			 * filled out unless we call this.
+			 */
+			p4_filelog(rev_ints->revision->file->codeline);
+			item = find_codeline_changeset(&changesets,
+						       rev_ints->revision->file->codeline);
+			if (!item->changeset ||
+			    item->changeset->number < rev_ints->revision->changeset->number) {
+				if (0)
+					printf("progress %lu integrates %s#%lu from %lu\n",
+					       changeset->number,
+					       rev_ints->revision->file->name,
+					       rev_ints->revision->number,
+					       rev_ints->revision->changeset->number);
+				item->changeset = rev_ints->revision->changeset;
+			}
+			rev_ints = rev_ints->next;
+		}
+	}
+	/* We could issue a warning if the state of other files didn't
+	 * match and yet didn't get integrated, but that's a lot of
+	 * work and there's no good way to represent the case of a
+	 * commit contributing to but not being completely obsoleted
+	 * by another commit.
+	 */
+	changeset->integrated = changesets;
+	while (changesets) {
+		//printf("# integrate %lu from %lu\n", changeset->number, changesets->changeset->number);
+		changesets = changesets->next;
+	}
+}
+
+static void follow_codeline(struct p4_codeline *target)
+{
+	struct p4_codeline *posn;
+	if (target->filelog_done)
+		return;
+
+	p4_filelog(target);
+
+	if (0)
+		printf("progress resolving integrates\n");
+
+	/* Now resolve all the integrates in changesets */
+	for (posn = target->depot->codelines; posn; posn = posn->next) {
+		struct p4_changeset *changeset;
+		for (changeset = posn->unreported; changeset; changeset = changeset->next) {
+			resolve_changeset_integrates(changeset);
+		}
+		resolve_codeline_contents(posn);
+	}
+}
+
+static struct p4_codeline *import_depot(struct p4_depot *depot, const char *refname)
+{
+	struct p4_codeline *target;
+	char *path = refname_to_codeline(refname);
+	target = get_codeline(depot, path);
+
+	if (!target)
+		die("Invalid codeline: %s", path);
+
+	free(path);
+
+	follow_codeline(target);
+
+	return target;
+}
+
+static void name_changeset(struct p4_changeset *changeset)
+{
+	if (changeset->commit)
+		printf("%s\n", sha1_to_hex(changeset->commit->object.sha1));
+	else
+		printf(":%d\n", changeset->mark);
+}
+
+static void lookup_git_changeset(struct p4_codeline *codeline,
+				 struct p4_changeset *changeset)
+{
+	while (!changeset->commit) {
+		struct commit *parent = codeline->history->commit->parents->item;
+		parse_commit(parent);
+		codeline->history->previous->commit = parent;
+		codeline->history = codeline->history->previous;
+	}
+}
+
+static void report_codeline(struct p4_codeline *codeline,
+			    struct p4_changeset *until);
+
+static void identify_changeset(struct p4_changeset *changeset)
+{
+	if (changeset->mark || changeset->commit)
+		return;
+	if (changeset->codeline->finished_changeset >= changeset->number)
+		lookup_git_changeset(changeset->codeline, changeset);
+	else
+		report_codeline(changeset->codeline, changeset);
+}
+
+static int skip_found(struct p4_revision *revision,
+		      struct p4_revision_list **origin) {
+	struct p4_revision *orev = NULL;
+	struct p4_revision_list *i;
+	while (*origin) {
+		if (!strcmp((*origin)->revision->file->name,
+			    revision->file->name)) {
+			struct p4_revision_list *oitem = *origin;
+			*origin = oitem->next;
+			orev = oitem->revision;
+			free(oitem);
+			break;
+		} else {
+			origin = &((*origin)->next);
+		}
+	}
+	if (!revision->branch) /* It's changed anyway */
+		return 0;
+	for (i = revision->integrated; i; i = i->next) {
+		if (i->revision == orev)
+			return 1;
+	}
+	return 0;
+}
+
+static void report_codeline(struct p4_codeline *codeline, struct p4_changeset *until)
+{
+	struct p4_changeset *changeset;
+	struct p4_revision_list *rev;
+
+	printf("progress import codeline %s (%lu-%lu)\n", codeline->path,
+	       codeline->unreported->number, until->number);
+
+	for (changeset = codeline->unreported; changeset; changeset = changeset->next) {
+		struct p4_changeset_list *integrated = changeset->integrated;
+		struct p4_revision_list *origin = NULL;
+
+		while (integrated) {
+			identify_changeset(integrated->changeset);
+			integrated = integrated->next;
+		}
+		printf("progress import changeset %lu (%s)\n",
+		       changeset->number, changeset->codeline->path);
+		printf("# changeset %lu\n", changeset->number);
+		printf("commit %s\n", codeline->refname);
+		changeset->mark = codeline->depot->next_mark++;
+		printf("mark :%d\n", changeset->mark);
+		p4_change(changeset);
+		integrated = changeset->integrated;
+		if (changeset->previous) {
+			printf("from ");
+			name_changeset(changeset->previous);
+		} else if (integrated) {
+			printf("from ");
+			origin = copy_revision_list(integrated->changeset->contents);
+			name_changeset(integrated->changeset);
+			integrated = integrated->next;
+		}
+
+		while (integrated) {
+			printf("merge ");
+			name_changeset(integrated->changeset);
+			integrated = integrated->next;
+		}
+
+		for (rev = changeset->revisions; rev; rev = rev->next) {
+			if (rev->revision->delete) {
+				printf("D %s\n", rev->revision->file->name + 1);
+			} else if (!skip_found(rev->revision, &origin)) {
+				printf("M %s inline %s\n",
+				       get_file_mode(rev->revision->type),
+				       rev->revision->file->name + 1);
+				p4_print(rev->revision);
+			}
+		}
+		while (origin) {
+			struct p4_revision_list *old;
+			printf("D %s\n", origin->revision->file->name + 1);
+			old = origin;
+			origin = origin->next;
+			free(old);
+		}
+		printf("\n");
+		codeline->unreported = changeset->next;
+		if (changeset == until)
+			break;
+	}
+	printf("checkpoint\n");
+}
+
+static void import_p4(int ref_nr, const char **refs)
+{
+	int i;
+	struct p4_depot *depot = get_depot();
+	struct p4_codeline *target;
+	save_commit_buffer = 1;
+
+	for (i = 0; i < ref_nr; i++) {
+		target = import_depot(depot, refs[i]);
+
+		identify_changeset(target->head);
+	}
+}
+
+static void export_change(struct diff_options *options,
+			  unsigned old_mode, unsigned new_mode,
+			  const unsigned char *old_sha1,
+			  const unsigned char *new_sha1,
+			  const char *path)
+{
+	struct p4_codeline *codeline = options->format_callback_data;
+	p4_edit(codeline, path);
+	write_blob(codeline, new_sha1, path);
+}
+
+static void export_add_remove(struct diff_options *options,
+			      int addremove, unsigned mode,
+			      const unsigned char *sha1,
+			      const char *path)
+{
+	struct p4_codeline *codeline = options->format_callback_data;
+	if (addremove == '+') {
+		write_blob(codeline, sha1, path);
+		p4_add(codeline, path);
+	} else if (addremove == '-') {
+		p4_delete(codeline, path);
+	}
+}
+
+static void export_commit(struct p4_codeline *codeline,
+			  struct commit *git_commit, struct commit *git_parent)
+{
+	struct tree_desc pre, post;
+	struct diff_options opts;
+	memset(&opts, 0, sizeof(opts));
+	parse_tree(git_commit->tree);
+	parse_tree(git_parent->tree);
+	init_tree_desc(&pre, git_parent->tree->buffer, git_parent->tree->size);
+	init_tree_desc(&post, git_commit->tree->buffer, git_commit->tree->size);
+	opts.change = export_change;
+	opts.add_remove = export_add_remove;
+	opts.format_callback_data = codeline;
+	opts.flags = DIFF_OPT_RECURSIVE;
+	diff_tree(&pre, &post, "/", &opts);
+	p4_submit(git_commit);
+}
+
+static void export_p4(struct remote *remote, const char *branch)
+{
+	struct p4_depot *depot = get_depot();
+	const char *codeline = remote->url[0];
+	struct p4_codeline *target;
+	struct strbuf buf;
+
+	// check client
+
+	target = import_depot(depot, codeline);
+
+	strbuf_init(&buf, 0);
+
+	while (!strbuf_getline(&buf, stdin, '\n')) {
+		struct p4_changeset *parent = NULL, *integrate = NULL;
+		unsigned char sha1[20];
+		struct commit *commit, *git_parent = NULL;
+		struct commit_list *parents;
+		get_sha1(buf.buf, sha1);
+		commit = lookup_commit(sha1);
+		parse_commit(commit);
+		for (parents = commit->parents; parents; parents = parents->next) {
+			struct p4_changeset *p4_parent =
+				changeset_from_commit(depot, parents->item);
+			if (p4_parent) {
+				if (p4_parent->codeline == target) {
+					parent = p4_parent;
+					git_parent = parents->item;
+				} else
+					integrate = p4_parent;
+			}
+		}
+		if (target->head != parent) {
+			printf("progress not up-to-date\n");
+			return;
+		}
+		if (p4_where(target))
+			break;
+		p4_sync(target);
+
+		if (!parent) {
+			// Need to start new codeline
+		}
+		export_commit(target, commit, git_parent);
+	}
+}
+
+static const char **env;
+static int env_nr;
+static int env_alloc;
+
+static const char **codelines;
+static int codeline_nr;
+static int codeline_alloc;
+
+static int handle_config(const char *key, const char *value, void *cb)
+{
+	struct remote *remote = cb;
+	struct strbuf buf;
+	const char *subkey = NULL;
+
+	if (!prefixcmp(key, "vcs-p4."))
+		subkey = key + strlen("vcs-p4.");
+
+	if (remote && !prefixcmp(key, "remote.") &&
+	    !prefixcmp(key + strlen("remote."), remote->name))
+	    subkey = key + strlen("remote.") + strlen(remote->name) + 1;
+
+	if (!subkey)
+		return 0;
+
+	if (!strcmp(subkey, "findbranches")) {
+		find_new_codelines = git_config_bool(key, value);
+	}
+	if (!strcmp(subkey, "ignorecodeline")) {
+		ALLOC_GROW(ignore_codelines, ignore_codeline_nr + 1,
+			   ignore_codeline_alloc);
+		ignore_codelines[ignore_codeline_nr++] = xstrdup(value);
+	}
+	if (!strcmp(subkey, "port")) {
+		strbuf_init(&buf, 0);
+		strbuf_addf(&buf, "P4PORT=%s", value);
+
+		ALLOC_GROW(env, env_nr + 1, env_alloc);
+		env[env_nr++] = strbuf_detach(&buf, NULL);
+	}
+	if (!strcmp(subkey, "client")) {
+		strbuf_init(&buf, 0);
+		strbuf_addf(&buf, "P4CLIENT=%s", value);
+
+		ALLOC_GROW(env, env_nr + 1, env_alloc);
+		env[env_nr++] = strbuf_detach(&buf, NULL);
+	}
+	if (!strcmp(subkey, "codelineformat")) {
+		codeline_regex = (regex_t*)xmalloc(sizeof(regex_t));
+		if (regcomp(codeline_regex, value, REG_EXTENDED)) {
+			free(codeline_regex);
+			fprintf(stderr, "Invalid codeline pattern: %s",
+				value);
+		}
+	}
+	if (!strcmp(subkey, "codeline")) {
+		ALLOC_GROW(codelines, codeline_nr + 1, codeline_alloc);
+		codelines[codeline_nr++] = xstrdup(value);
+	}
+	return 0;
+}
+
+int main(int argc, const char **argv)
+{
+	const char *prefix = NULL;
+	struct remote *remote;
+
+	if (argc < 2) {
+		fprintf(stderr, "Command needed");
+		return 1;
+	}
+
+	if (!strcmp(argv[1], "capabilities")) {
+		git_config(handle_config, NULL);
+
+		printf("import\n");
+		printf("find-new-branches\n");
+		printf("export\n");
+		printf("fork\n");
+		printf("merge\n");
+		return 0;
+	}
+
+	if (!strcmp(argv[1], "import")) {
+		prefix = setup_git_directory();
+		remote = remote_get(argv[2]);
+
+		git_config(handle_config, remote);
+
+		find_new_codelines = 0;
+
+		ALLOC_GROW(env, env_nr + 1, env_alloc);
+		env[env_nr++] = NULL;
+
+		p4_init(env);
+
+		import_p4(argc - 3, argv + 3);
+		p4_fini();
+		if (LIST_P4_OPERATIONS)
+			fprintf(stderr, "Prints done: %d\n", prints_done);
+		return 0;
+	}
+	if (!strcmp(argv[1], "list")) {
+		int i;
+
+		prefix = setup_git_directory();
+		remote = remote_get(argv[2]);
+
+		git_config(handle_config, remote);
+
+		ALLOC_GROW(env, env_nr + 1, env_alloc);
+		env[env_nr++] = NULL;
+
+		if (find_new_codelines) {
+			struct p4_depot *depot = get_depot();
+			struct p4_codeline *codeline;
+			save_commit_buffer = 1;
+
+			p4_init(env);
+
+			for (i = 0; i < codeline_nr; i++)
+				import_depot(depot,
+					     codeline_to_refname(codelines[i]));
+
+			for (codeline = depot->codelines; codeline;
+			     codeline = codeline->next) {
+				if (codeline->ignore)
+					continue;
+				follow_codeline(codeline);
+				printf("%s %s\n", codeline->refname,
+				       codeline->head == codeline->history ?
+				       "unchanged" : "changed");
+			}
+
+			p4_fini();
+		} else {
+			for (i = 0; i < codeline_nr; i++)
+				printf("%s\n",
+				       codeline_to_refname(codelines[i]));
+		}
+		return 0;
+	}
+	if (!strcmp(argv[1], "export")) {
+		prefix = setup_git_directory();
+
+		remote = remote_get(argv[2]);
+
+		git_config(handle_config, remote);
+
+		ALLOC_GROW(env, env_nr + 1, env_alloc);
+		env[env_nr++] = NULL;
+
+		p4_init(env);
+
+		export_p4(remote, argv[3]);
+		// 1: check whether the import of the target location
+		//    is up-to-date
+
+		// 2: find the target location in the client view
+
+		// 3: bring the client view up-to-date with the target
+		//    location
+
+		// 4: recheck that this matches the tree
+
+		// 5: open the necessary files in the client
+
+		// 6: replace the necessary files in the filesystem
+
+		// 7: submit
+
+		// 8: reimport
+
+		// 9: go back to (3)
+		p4_fini();
+	}
+	return 1;
+}
diff --git a/vcs-p4/vcs-p4.h b/vcs-p4/vcs-p4.h
new file mode 100644
index 0000000..57ad475
--- /dev/null
+++ b/vcs-p4/vcs-p4.h
@@ -0,0 +1,128 @@
+#ifndef VCS_P4_H
+#define VCS_P4_H
+
+struct p4_depot {
+	struct p4_codeline *codelines;
+
+	int next_mark;
+};
+
+/** Note that multiple codelines can have changesets with the same
+ * number.
+ **/
+struct p4_changeset {
+	struct p4_codeline *codeline;
+
+	unsigned long number;
+
+	/** Used only if a previous import found this changeset **/
+	struct commit *commit;
+
+	/** Used only if this changeset is newly imported in this operation. **/
+	int mark;
+
+	const char *message;
+
+	/** These are the revisions introduced in the changeset **/
+	struct p4_revision_list *revisions;
+
+	/** These are the revisions which are current as of the changeset **/
+	struct p4_revision_list *contents;
+
+	/** Not explicit in p4 **/
+	struct p4_changeset_list *integrated;
+
+	/** Next and previous in codeline **/
+	struct p4_changeset *next;
+	struct p4_changeset *previous;
+};
+
+struct p4_changeset_list {
+	struct p4_changeset *changeset;
+	struct p4_changeset_list *next;
+};
+
+struct p4_revision {
+	unsigned long number;
+
+	unsigned delete : 1;
+	unsigned branch : 1; /* unchanged against something integrated */
+
+	const char *type;
+
+	struct p4_file *file;
+	struct p4_changeset *changeset;
+
+	struct p4_revision_list *integrated;
+
+	/** Next in file **/
+	struct p4_revision *next;
+};
+
+/** Represents a collection of revisions of different files
+ **/
+struct p4_revision_list {
+	struct p4_revision *revision;
+	struct p4_revision_list *next;
+};
+
+struct p4_file {
+	struct p4_codeline *codeline;
+	const char *name;
+
+	unsigned head_number;
+
+	struct p4_revision *revisions;
+
+	/** Next file in codeline **/
+	struct p4_file *next;
+};
+
+/** perforce doesn't record codelines; we have to reverse-engineer
+ * them from how people seem to be branching.
+ **/
+struct p4_codeline {
+	unsigned ignore : 1;
+
+	struct p4_depot *depot;
+
+	/** Base path of codeline **/
+	const char *path;
+
+	/** git refname to import into **/
+	const char *refname;
+
+	struct p4_file *files;
+	struct p4_changeset *changesets;
+
+	int filelog_done;
+
+	/* The incremental state is that we have some changeset that
+	 * we previously imported up to, and we have git history going
+	 * back from that point, of which we've looked up some and
+	 * could look up more as needed. Also, there's p4-only history
+	 * going forward after the common history, and we've imported
+	 * some of that, and could import more as needed. Since
+	 * codelines are sorted by changeset number, we can tell which
+	 * way to go to get a name for a changeset.
+	 */
+	struct p4_changeset *history;
+	struct p4_changeset *unreported;
+
+	struct p4_changeset *head;
+
+	unsigned long finished_changeset;
+
+	/** For reporting **/
+	unsigned long num_changesets;
+
+	/** Next codeline in depot **/
+	struct p4_codeline *next;
+
+	/** Filesystem location of working directory for this codeline
+	 * on the client.
+	 **/
+	char *working;
+};
+
+#endif
-- 
1.6.2.1.476.g9bf04b

^ permalink raw reply related

* [PATCH 4/5] Draft of API for git-vcs-*, transport.c code to use it.
From: Daniel Barkalow @ 2009-03-25  3:04 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

---
 Documentation/git-vcs.txt |   93 ++++++++++++++++++++++++++++++++++++++
 transport.c               |  109 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 202 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/git-vcs.txt

diff --git a/Documentation/git-vcs.txt b/Documentation/git-vcs.txt
new file mode 100644
index 0000000..fa02b76
--- /dev/null
+++ b/Documentation/git-vcs.txt
@@ -0,0 +1,93 @@
+git-vcs-*(1)
+============
+
+NAME
+----
+git-vcs-* - Helper programs for interoperation with foreign systems
+
+SYNOPSIS
+--------
+'git vcs-<system>' <command> [options] [arguments]
+
+DESCRIPTION
+-----------
+
+These programs are normally not used directly by end users, but are
+invoked by various git programs that interact with remote repositories
+when the repository they would operate on is part of a foreign
+system.
+
+Each 'git vcs-<system>' is a helper for interoperating with a
+particular version control system. Different helpers have different
+capabilities (limited both be the particular helper and by the
+capabilities of the system they connect to), and they report what
+capabilities they support.
+
+In general, these programs interact with a portion of the refs
+namespace that isn't used by the rest of git. The configuration will
+then (generally) map these refs into the remotes namespace. This
+allows the importer to do whatever it wants with its refs without
+affecting the state visible to normal git programs.
+
+COMMANDS
+--------
+
+'capabilities'::
+	Prints the capabilities of the helper, one per line. These are:
+	 - import: the basic import command
+	 - marks: import should be done with a saved marks file
+	 - find-new-branches: detect new branches
+	 - export: the general export command
+	 - fork: create a new branch and export to it
+	 - anonymous-fork: make commits on a branch without an inherent name
+	 - merge: merge branches (of whatever type the system supports)
+
+	If the helper doesn't support "merge", the default for pull is
+	to rebase instead of merging.
+
+'list'::
+	Takes the remote name, and outputs the names of refs. These
+	may be followed, after a single space, by "changed" or
+	"unchanged", indicating whether the foreign repository has
+	changed from the state in the ref. If the helper doesn't know,
+	it doesn't have to provide a value. (In particular, it
+	shouldn't do expensive operations, such as importing the
+	content, to see whether it matches.)
+
+'import'::
+	Takes the remote name and a list of names of refs, and imports
+	whatever it describes, by outputting it in git-fast-import
+	format.
+
+'export'::
+	Sends the branch to the foreign system and reimports it in
+	fast-import format.
+
+	Reads a list of commits from stdin, where each commit has no
+	parents which were neither produced by an earlier import nor
+	appearing earlier in the list, where some commit has the old
+	value of the branch as a parent, and where all commits listed
+	are ancestors of the last one. Furthermore:
+
+	 - if the system doesn't support merges, each of these commits
+	   has only a single parent;
+
+	 - if the system doesn't support anonymous branches, the first
+	   commit has the old value of the branch as a parent (if the
+	   branch already had a value), and all parents are either the
+	   commit listed immediately before or produced by an earlier
+	   import;
+
+	 - if the system doesn't support many-way merges, each commit
+	   has at most two parents.
+
+	export produces output in fast-import format giving the
+	content after a round-trip through the foreign system. This
+	also contains extra headers to report the mapping of original
+	git commits to reimported git commits (to facilitate rewriting
+	local branches to use the history-as-reimported instead of the
+	git-only version).
+
+	export reports how much it managed to export by producing
+	commits in the fast-import stream that replace the listed
+	items that were successfully exported.
diff --git a/transport.c b/transport.c
index 8a37db5..fe78169 100644
--- a/transport.c
+++ b/transport.c
@@ -916,6 +916,113 @@ static int disconnect_git(struct transport *transport)
 	return 0;
 }
 
+static int fetch_refs_via_foreign(struct transport *transport,
+				  int nr_heads, struct ref **to_fetch)
+{
+	struct remote *remote = transport->remote;
+	struct child_process importer;
+	struct child_process fastimport;
+	struct ref *posn;
+	int i, count;
+	struct strbuf buf;
+
+	memset(&importer, 0, sizeof(importer));
+	importer.in = 0;
+	importer.no_stdin = 1;
+	importer.out = -1;
+	importer.err = 0;
+	importer.argv = xcalloc(5 + nr_heads, sizeof(*importer.argv));
+	strbuf_init(&buf, 80);
+	strbuf_addf(&buf, "vcs-%s", remote->foreign_vcs);
+	importer.argv[0] = buf.buf;
+	importer.argv[1] = "import";
+	importer.argv[2] = remote->name;
+	count = 0;
+	for (i = 0; i < nr_heads; i++) {
+		posn = to_fetch[i];
+		if (posn->status & REF_STATUS_UPTODATE)
+			continue;
+		importer.argv[3 + count] = posn->name;
+		count++;
+	}
+	importer.git_cmd = 1;
+	if (count) {
+		start_command(&importer);
+
+		memset(&fastimport, 0, sizeof(fastimport));
+		fastimport.in = importer.out;
+		fastimport.argv = xcalloc(3, sizeof(*fastimport.argv));
+		fastimport.argv[0] = "fast-import";
+		fastimport.argv[1] = "--quiet";
+		fastimport.git_cmd = 1;
+		start_command(&fastimport);
+
+		finish_command(&importer);
+		finish_command(&fastimport);
+	}
+	strbuf_release(&buf);
+	for (i = 0; i < nr_heads; i++) {
+		posn = to_fetch[i];
+		if (posn->status & REF_STATUS_UPTODATE)
+			continue;
+		read_ref(posn->name, posn->old_sha1);
+		count++;
+	}
+	return 0;
+}
+
+static struct ref *get_refs_via_foreign(struct transport *transport)
+{
+	struct remote *remote = transport->remote;
+	struct child_process importer;
+	struct ref *ret = NULL;
+	struct ref **end = &ret;
+	struct strbuf buf;
+	memset(&importer, 0, sizeof(importer));
+	importer.in = 0;
+	importer.no_stdin = 1;
+	importer.out = -1;
+	importer.err = 0;
+	importer.argv = xcalloc(5, sizeof(*importer.argv));
+	strbuf_init(&buf, 80);
+	strbuf_addf(&buf, "vcs-%s", remote->foreign_vcs);
+	importer.argv[0] = buf.buf;
+	importer.argv[1] = "list";
+	importer.argv[2] = remote->name;
+	importer.git_cmd = 1;
+	start_command(&importer);
+
+	strbuf_reset(&buf);
+	while (1) {
+		char *eol, *eon;
+		if (strbuf_read(&buf, importer.out, 80) <= 0)
+			break;
+		while (1) {
+			eol = strchr(buf.buf, '\n');
+			if (!eol)
+				break;
+			*eol = '\0';
+			eon = strchr(buf.buf, ' ');
+			if (eon)
+				*eon = '\0';
+			*end = alloc_ref(buf.buf);
+			if (eon) {
+				if (strstr(eon + 1, "unchanged")) {
+					(*end)->status |= REF_STATUS_UPTODATE;
+					if (read_ref((*end)->name, (*end)->old_sha1))
+						die("Unchanged?");
+				}
+			}
+			end = &((*end)->next);
+			strbuf_remove(&buf, 0, eol - buf.buf + 1);
+		}
+	}
+
+	finish_command(&importer);
+	strbuf_release(&buf);
+	return ret;
+}
+
 static int is_local(const char *url)
 {
 	const char *colon = strchr(url, ':');
@@ -940,6 +1047,8 @@ struct transport *transport_get(struct remote *remote, const char *url)
 	ret->url = url;
 
 	if (remote && remote->foreign_vcs) {
+		ret->get_refs_list = get_refs_via_foreign;
+		ret->fetch = fetch_refs_via_foreign;
 	} else if (!prefixcmp(url, "rsync:")) {
 		ret->get_refs_list = get_refs_via_rsync;
 		ret->fetch = fetch_objs_via_rsync;
-- 
1.6.2.1.476.g9bf04b

^ permalink raw reply related

* [PATCH 3/5] Add option for using a foreign VCS
From: Daniel Barkalow @ 2009-03-25  3:04 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

This simply configures the remote to use a transport that doesn't have
any methods at all and is therefore unable to do anything yet.

Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>
---
 Documentation/config.txt |    4 ++++
 remote.c                 |    2 ++
 remote.h                 |    2 ++
 transport.c              |    3 ++-
 4 files changed, 10 insertions(+), 1 deletions(-)

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 089569a..14b0e07 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1305,6 +1305,10 @@ remote.<name>.tagopt::
 	Setting this value to \--no-tags disables automatic tag following when
 	fetching from remote <name>
 
+remote.<name>.vcs::
+	Setting this to a value <vcs> will cause git to interact with
+	the remote with the git-vcs-<vcs> helper.
+
 remotes.<group>::
 	The list of remotes which are fetched by "git remote update
 	<group>".  See linkgit:git-remote[1].
diff --git a/remote.c b/remote.c
index 2b037f1..be04658 100644
--- a/remote.c
+++ b/remote.c
@@ -411,6 +411,8 @@ static int handle_config(const char *key, const char *value, void *cb)
 	} else if (!strcmp(subkey, ".proxy")) {
 		return git_config_string((const char **)&remote->http_proxy,
 					 key, value);
+	} else if (!strcmp(subkey, ".vcs")) {
+		return git_config_string(&remote->foreign_vcs, key, value);
 	}
 	return 0;
 }
diff --git a/remote.h b/remote.h
index de3d21b..e77dc1b 100644
--- a/remote.h
+++ b/remote.h
@@ -11,6 +11,8 @@ struct remote {
 	const char *name;
 	int origin;
 
+	const char *foreign_vcs;
+
 	const char **url;
 	int url_nr;
 	int url_alloc;
diff --git a/transport.c b/transport.c
index 26c578e..8a37db5 100644
--- a/transport.c
+++ b/transport.c
@@ -939,7 +939,8 @@ struct transport *transport_get(struct remote *remote, const char *url)
 	ret->remote = remote;
 	ret->url = url;
 
-	if (!prefixcmp(url, "rsync:")) {
+	if (remote && remote->foreign_vcs) {
+	} else if (!prefixcmp(url, "rsync:")) {
 		ret->get_refs_list = get_refs_via_rsync;
 		ret->fetch = fetch_objs_via_rsync;
 		ret->push = rsync_transport_push;
-- 
1.6.2.1.476.g9bf04b

^ permalink raw reply related

* [PATCH 2/5] Document details of transport function APIs
From: Daniel Barkalow @ 2009-03-25  3:04 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

In particular, explain which of the fields of struct ref is used for
what purpose in the input to and output from each function.

Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>
---
 transport.h |   38 ++++++++++++++++++++++++++++++++++++++
 1 files changed, 38 insertions(+), 0 deletions(-)

diff --git a/transport.h b/transport.h
index 489e96a..2e1650a 100644
--- a/transport.h
+++ b/transport.h
@@ -18,11 +18,49 @@ struct transport {
 	int (*set_option)(struct transport *connection, const char *name,
 			  const char *value);
 
+	/**
+	 * Returns a list of the remote side's refs. In order to allow
+	 * the transport to try to share connections, for_push is a
+	 * hint as to whether the ultimate operation is a push or a fetch.
+	 *
+	 * If the transport is able to determine the remote hash for
+	 * the ref without a huge amount of effort, it should store it
+	 * in the ref's old_sha1 field; otherwise it should be all 0.
+	 **/
 	struct ref *(*get_refs_list)(struct transport *transport, int for_push);
+
+	/**
+	 * Fetch the objects for the given refs. Note that this gets
+	 * an array, and should ignore the list structure.
+	 *
+	 * If the transport did not get hashes for refs in
+	 * get_refs_list(), it should set the old_sha1 fields in the
+	 * provided refs now.
+	 **/
 	int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
+
+	/**
+	 * Push the objects and refs. Send the necessary objects, and
+	 * then tell the remote side to update each ref in the list
+	 * from old_sha1 to new_sha1.
+	 *
+	 * Where possible, set the status for each ref appropriately.
+	 *
+	 * If, in the process, the transport determines that the
+	 * remote side actually responded to the push by updating the
+	 * ref to a different value, the transport should modify the
+	 * new_sha1 in the ref. (Note that this is a matter of the
+	 * remote accepting but rewriting the change, not rejecting it
+	 * and reporting that a different update had already taken
+	 * place)
+	 **/
 	int (*push_refs)(struct transport *transport, struct ref *refs, int flags);
 	int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
 
+	/** get_refs_list(), fetch(), and push_refs() can keep
+	 * resources (such as a connection) reserved for futher
+	 * use. disconnect() releases these resources.
+	 **/
 	int (*disconnect)(struct transport *connection);
 	char *pack_lockfile;
 	signed verbose : 2;
-- 
1.6.2.1.476.g9bf04b

^ permalink raw reply related

* [PATCH 1/5] Allow late reporting of fetched hashes
From: Daniel Barkalow @ 2009-03-25  3:04 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Some future transports (in particular, foreign VCS importers) will
only report the hashes of new commits when the objects are also
available. In preparation, allow fetch_refs() to modify the refs it
gets (in particular, the remote side's sha1), and treat the null sha1,
when reported by get_ref_list(), as different from any value,
including itself (which, when local, indicates that the local version
doesn't exist yet).

Signed-off-by: Daniel Barkalow <barkalow@iabervon.org>
---
 builtin-clone.c |    6 ++++--
 transport.c     |   17 +++++++++--------
 transport.h     |    4 ++--
 3 files changed, 15 insertions(+), 12 deletions(-)

diff --git a/builtin-clone.c b/builtin-clone.c
index 0031b5f..ab51974 100644
--- a/builtin-clone.c
+++ b/builtin-clone.c
@@ -485,8 +485,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 					     option_upload_pack);
 
 		refs = transport_get_remote_refs(transport);
-		if(refs)
-			transport_fetch_refs(transport, refs);
+		if (refs) {
+			struct ref *ref_cpy = copy_ref_list(refs);
+			transport_fetch_refs(transport, ref_cpy);
+		}
 	}
 
 	if (refs) {
diff --git a/transport.c b/transport.c
index 3dfb03c..26c578e 100644
--- a/transport.c
+++ b/transport.c
@@ -207,7 +207,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
 }
 
 static int fetch_objs_via_rsync(struct transport *transport,
-				int nr_objs, const struct ref **to_fetch)
+				int nr_objs, struct ref **to_fetch)
 {
 	struct strbuf buf = STRBUF_INIT;
 	struct child_process rsync;
@@ -356,7 +356,7 @@ static int rsync_transport_push(struct transport *transport,
 
 #ifndef NO_CURL /* http fetch is the only user */
 static int fetch_objs_via_walker(struct transport *transport,
-				 int nr_objs, const struct ref **to_fetch)
+				 int nr_objs, struct ref **to_fetch)
 {
 	char *dest = xstrdup(transport->url);
 	struct walker *walker = transport->data;
@@ -523,7 +523,7 @@ static struct ref *get_refs_via_curl(struct transport *transport, int for_push)
 }
 
 static int fetch_objs_via_curl(struct transport *transport,
-				 int nr_objs, const struct ref **to_fetch)
+				 int nr_objs, struct ref **to_fetch)
 {
 	if (!transport->data)
 		transport->data = get_http_walker(transport->url,
@@ -563,7 +563,7 @@ static struct ref *get_refs_from_bundle(struct transport *transport, int for_pus
 }
 
 static int fetch_refs_from_bundle(struct transport *transport,
-			       int nr_heads, const struct ref **to_fetch)
+			       int nr_heads, struct ref **to_fetch)
 {
 	struct bundle_transport_data *data = transport->data;
 	return unbundle(&data->header, data->fd);
@@ -641,7 +641,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus
 }
 
 static int fetch_refs_via_pack(struct transport *transport,
-			       int nr_heads, const struct ref **to_fetch)
+			       int nr_heads, struct ref **to_fetch)
 {
 	struct git_transport_data *data = transport->data;
 	char **heads = xmalloc(nr_heads * sizeof(*heads));
@@ -1046,15 +1046,16 @@ const struct ref *transport_get_remote_refs(struct transport *transport)
 	return transport->remote_refs;
 }
 
-int transport_fetch_refs(struct transport *transport, const struct ref *refs)
+int transport_fetch_refs(struct transport *transport, struct ref *refs)
 {
 	int rc;
 	int nr_heads = 0, nr_alloc = 0;
-	const struct ref **heads = NULL;
-	const struct ref *rm;
+	struct ref **heads = NULL;
+	struct ref *rm;
 
 	for (rm = refs; rm; rm = rm->next) {
 		if (rm->peer_ref &&
+		    !is_null_sha1(rm->old_sha1) &&
 		    !hashcmp(rm->peer_ref->old_sha1, rm->old_sha1))
 			continue;
 		ALLOC_GROW(heads, nr_heads + 1, nr_alloc);
diff --git a/transport.h b/transport.h
index b1c2252..489e96a 100644
--- a/transport.h
+++ b/transport.h
@@ -19,7 +19,7 @@ struct transport {
 			  const char *value);
 
 	struct ref *(*get_refs_list)(struct transport *transport, int for_push);
-	int (*fetch)(struct transport *transport, int refs_nr, const struct ref **refs);
+	int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
 	int (*push_refs)(struct transport *transport, struct ref *refs, int flags);
 	int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
 
@@ -71,7 +71,7 @@ int transport_push(struct transport *connection,
 
 const struct ref *transport_get_remote_refs(struct transport *transport);
 
-int transport_fetch_refs(struct transport *transport, const struct ref *refs);
+int transport_fetch_refs(struct transport *transport, struct ref *refs);
 void transport_unlock_pack(struct transport *transport);
 int transport_disconnect(struct transport *transport);
 
-- 
1.6.2.1.476.g9bf04b

^ permalink raw reply related

* [PATCH 0/5] Foreign VCS as remote config
From: Daniel Barkalow @ 2009-03-25  3:04 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

This series, like my previous series, makes it possible to set up a git 
config like:

[remote "origin"]
        vcs = p4
        port = localhost:1666
        ignoreCodeline = //depot/projects/oof-1.0
        codeline = //depot/projects/foo-1.0
        fetch = refs/p4/depot/projects/*:refs/remotes/origin/*

And then "git fetch origin" will actually incrementally import a p4 
project.

The main difference from the previous series, aside from a nicer version 
of the p4 importer that can use the free-as-in-beer C++ API, is the fact 
that the only diff for a builtin command is to builtin-clone, which just 
needs to make a copy of a const struct before passing it to a function 
that can now change it.

Parts 1 and 2 are potentially useful for other other protocols 
transporting git data, and could be applied now. Part 3 creates the option 
and makes it not work at all. Part 4 is the same old API, and transport.c 
code to use it. Part 5 is my p4 example.

There's still the oddity that foreign systems that don't use a URL will 
lead to struct remotes that don't look valid to some git code that expects 
the URL field to be filled.

Daniel Barkalow (5):
  Allow late reporting of fetched hashes
  Document details of transport function APIs
  Add option for using a foreign VCS
  Draft of API for git-vcs-*, transport.c code to use it.
  p4 example of git-vcs API for fetch direction

 Documentation/config.txt     |    4 +
 Documentation/git-vcs-p4.txt |   33 ++
 Documentation/git-vcs.txt    |   93 ++++
 Makefile                     |   25 +
 builtin-clone.c              |    6 +-
 p4-notes                     |   33 ++
 remote.c                     |    2 +
 remote.h                     |    2 +
 transport.c                  |  129 ++++-
 transport.h                  |   42 ++-
 vcs-p4/p4client-api.cc       |  169 ++++++
 vcs-p4/p4client.c            |  137 +++++
 vcs-p4/p4client.h            |   38 ++
 vcs-p4/vcs-p4.c              | 1229 ++++++++++++++++++++++++++++++++++++++++++
 vcs-p4/vcs-p4.h              |  128 +++++
 15 files changed, 2057 insertions(+), 13 deletions(-)
 create mode 100644 Documentation/git-vcs-p4.txt
 create mode 100644 Documentation/git-vcs.txt
 create mode 100644 p4-notes
 create mode 100644 vcs-p4/p4client-api.cc
 create mode 100644 vcs-p4/p4client.c
 create mode 100644 vcs-p4/p4client.h
 create mode 100644 vcs-p4/vcs-p4.c
 create mode 100644 vcs-p4/vcs-p4.h

^ permalink raw reply

* Re: [PATCH] difftool: add various git-difftool tests
From: Junio C Hamano @ 2009-03-25  2:42 UTC (permalink / raw)
  To: David Aguilar; +Cc: gitster, git
In-Reply-To: <1237797676-32047-2-git-send-email-davvid@gmail.com>

David Aguilar <davvid@gmail.com> writes:

> +remove_config_vars()
> +{
> +	# Unset all config variables used by git-difftool
> +	git config --unset diff.tool
> +	git config --unset difftool.test-tool.cmd
> +	git config --unset merge.tool
> +	git config --unset mergetool.test-tool.cmd
> +	return 0
> +}
> +
> +restore_test_defaults()
> +{
> +	# Restores the test defaults used by several tests
> +	remove_config_vars
> +	unset GIT_DIFF_TOOL &&
> +	unset GIT_MERGE_TOOL &&
> +	unset GIT_DIFFTOOL_NO_PROMPT &&

I thought some shells' "unset" returns non-zero status when is given an
already unset variable.  I suspect you would want to drop the && chain
just like you did for remove_config_vars for the same reason.

> +	git config diff.tool test-tool &&
> +	git config difftool.test-tool.cmd "cat \$LOCAL"
> +}

'cat $LOCAL' would be much easier to read, wouldn't it?

> + ...
> +# Specify the diff tool using $GIT_DIFF_TOOL
> +test_expect_success 'GIT_DIFF_TOOL variable' '
> +	git config --unset diff.tool &&

You might want to lose && here in case the user told an earlier test that
sets the configuration to some value skipped (or such test failed), or
perhaps later somebody adds tests before this one that leaves the config
without this variable.

Other than that I didn't see major breakages.

Thanks.

^ permalink raw reply

* Re: [PATCH] init-db: support --import to add all files and commit  right after init
From: Nguyen Thai Ngoc Duy @ 2009-03-25  2:27 UTC (permalink / raw)
  To: Shawn O. Pearce; +Cc: git
In-Reply-To: <20090325021458.GY23521@spearce.org>

On Wed, Mar 25, 2009 at 1:14 PM, Shawn O. Pearce <spearce@spearce.org> wrote:
> Nguy???n Th??i Ng???c Duy <pclouds@gmail.com> wrote:
>> This is equivalent to "git init;git add .;git commit -q -m blah".
>> I find myself doing that too many times, hence this shortcut.
>
> Why not:
>
>  git config --global alias.init-import '!git init && git add . && git commit -q'

1. less setup on new machine
2. fast-import support (not yet though)
-- 
Duy

^ permalink raw reply

* Re: [BUG?] How to make a shared/restricted repo?
From: Junio C Hamano @ 2009-03-25  2:24 UTC (permalink / raw)
  To: Johan Herland; +Cc: git
In-Reply-To: <200903250311.56300.johan@herland.net>

Johan Herland <johan@herland.net> writes:

> On Wednesday 25 March 2009, Junio C Hamano wrote:
>> You might like to try a patch like this (untested).
>>
>>  path.c |   17 +++++------------
>>  1 files changed, 5 insertions(+), 12 deletions(-)
>
> Thanks!
>
> This works much better :)
>
> However, there are still some questions/issues:
>
> - t1301-shared-repo.sh fails:
>     Oops, .git/HEAD is not 0664 but -rw-rw---- [...]
>     * FAIL 3: shared=1 does not clear bits preset by umask 022
>   (I guess this is expected, as your patch changes the assumptions)

I'd rather say the patch breaks people's expectations.

^ permalink raw reply

* Re: [PATCH] init-db: support --import to add all files and commit right after init
From: Shawn O. Pearce @ 2009-03-25  2:14 UTC (permalink / raw)
  To: Nguy???n Th??i Ng???c Duy; +Cc: git
In-Reply-To: <1237946996-5287-1-git-send-email-pclouds@gmail.com>

Nguy???n Th??i Ng???c Duy <pclouds@gmail.com> wrote:
> This is equivalent to "git init;git add .;git commit -q -m blah".
> I find myself doing that too many times, hence this shortcut.

Why not:

  git config --global alias.init-import '!git init && git add . && git commit -q'

?

-- 
Shawn.

^ permalink raw reply

* Re: branch ahead in commits but push claims all up to date
From: John Tapsell @ 2009-03-25  2:13 UTC (permalink / raw)
  To: Irene Ros; +Cc: Daniel Barkalow, git
In-Reply-To: <7001b7a00903241901w107e2973i9912eab114c9cde0@mail.gmail.com>

2009/3/25 Irene Ros <imirene@gmail.com>:
> Hi All,
>
> Thank you for the good advice. I may be the case I am somehow misusing

So, did you actually try:

git fetch

?

> git... I couldn't resolve the issue and so I created a new project off
> of the same repo. Switching to the same branch in question yielded an
> even stranger result: In this new project, the commits were there (I
> could see them in git log and in git log origin/myBranch) whereas in
> the previous older project I did not... does that make sense? Our
> origin branches are located on a central server so can't quite figure
> out why viewing the log of the same remote branch from two different
> projects would yield different results. Any suggestions? At this
> point, I'm just really curious.
>
> -- Irene
>
>
>
> On Tue, Mar 24, 2009 at 9:24 PM, Daniel Barkalow <barkalow@iabervon.org> wrote:
>> On Wed, 25 Mar 2009, John Tapsell wrote:
>>
>>> 2009/3/24 Daniel Barkalow <barkalow@iabervon.org>:
>>> > On Tue, 24 Mar 2009, John Tapsell wrote:
>>> >
>>> >> 2009/3/24 Irene Ros <imirene@gmail.com>:
>>> >> > Hi all,
>>> >> >
>>> >> > I've been using git for some time now and haven't run into this issue
>>> >> > before, perhaps someone else here has:
>>> >> >
>>> >> > I have a branch that is ahead of its origin by a few commits:
>>> >> >
>>> >> > $ git status
>>> >> > # On branch myBranch
>>> >> > # Your branch is ahead of 'origin/myBranch' by 10 commits.
>>> >>
>>> >> Tried running: git fetch   ?
>>> >>
>>> >> For some weird reason  "git push origin mybranch"  doesn't actually
>>> >> update origin/mybranch.  It's more annoying :-)
>>> >
>>> > It should, so long as you're using the native transport and
>>> > origin/mybranch actually tracks mybranch on origin.
>>> >
>>> > "git push" doesn't update it, but the code that implements the native
>>> > transport does update it if it succeeds.
>>> >
>>> > (Actually, I'm not 100% sure that, if you update origin through some other
>>> > channel with exactly the commit that you now have in mybranch locally, and
>>> > then try the push, it will update the local tracking for that branch; is
>>> > that what you've hit?)
>>>
>>> I update via http - maybe that's why?  origin/mybranch is never
>>> updated when I push.  It's not just a once-off quirk.
>>
>> Yup, http doesn't have it. One of my series currently in next moves it
>> from the git-specific protocol to the common code, but there's still work
>> to be done to allow the http push transport to report back to the common
>> code what got updated successfully, which is largely a matter of making
>> the http-push code run in-process instead of as a command run by
>> transport.c, and using the just-added API.
>>
>>        -Daniel
>> *This .sig left intentionally blank*
>

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox