Merge branch 'master' of git://people.freedesktop.org/~hausmann/git-p4
authorJunio C Hamano <gitster@pobox.com>
Thu, 19 Jul 2007 00:23:03 +0000 (17:23 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 19 Jul 2007 00:23:03 +0000 (17:23 -0700)
* 'master' of git://people.freedesktop.org/~hausmann/git-p4:
git-p4: Cleanup, used common function for listing imported p4 branches
git-p4: Fix upstream branch detection for submit/rebase with multiple branches.
git-p4: Cleanup, make listExistingP4Branches a global function for later use.
git-p4: input to "p4 files" by stdin instead of arguments
git-p4: use subprocess in p4CmdList

22 files changed:
Documentation/RelNotes-1.5.3.txt
Documentation/asciidoc.conf
Documentation/git-branch.txt
Documentation/git-p4import.txt [deleted file]
Documentation/git-stash.txt
INSTALL
Makefile
builtin-mailinfo.c
contrib/emacs/Makefile
contrib/p4import/README [new file with mode: 0644]
contrib/p4import/git-p4import.py [new file with mode: 0644]
contrib/p4import/git-p4import.txt [new file with mode: 0644]
entry.c
git-filter-branch.sh
git-p4import.py [deleted file]
git-svn.perl
git.spec.in
t/lib-git-svn.sh
t/t7400-submodule-basic.sh
t/t9115-git-svn-dcommit-funky-renames.sh [new file with mode: 0755]
t/t9115/funky-names.dump [new file with mode: 0644]
unpack-trees.c
index 63e33b9d279f4f434b7772c7860a3915287aa9c1..896ff1d95a2a307fd764b66de4fbd882e64dd9c6 100644 (file)
@@ -21,6 +21,8 @@ Updates since v1.5.2
 
 * New commands and options.
 
+  - "git log --date=<format>" can use more formats: iso8601, rfc2822.
+
   - The hunk header output from "git diff" family can be customized
     with the attributes mechanism.  See gitattributes(5) for details.
 
@@ -68,6 +70,10 @@ Updates since v1.5.2
   - "git gc --aggressive" tells the command to spend more cycles
     to optimize the repository harder.
 
+  - "git repack" learned a "window-memory" limit which
+    dynamically reduces the window size to stay within the
+    specified memory usage.
+
   - "git repack" can be told to split resulting packs to avoid
     exceeding limit specified with "--max-pack-size".
 
@@ -99,6 +105,9 @@ Updates since v1.5.2
 
 * Updated behavior of existing commands.
 
+  - "git rm --cached" does not complain when removing a newly
+    added file from the index anymore.
+
   - "git svn dcommit" retains local merge information.
 
   - "git config" to set values also honors type flags like --bool
@@ -175,6 +184,11 @@ Updates since v1.5.2
     concatenate them into a single line and treat the result as
     "oneline".
 
+  - "git p4import" has been demoted to contrib status.  For
+    a superior option, checkout the git-p4 front end to
+    git-fast-import (also in contrib).  The man page and p4
+    rpm have been removed as well.
+
 * Builds
 
   - old-style function definitions (most notably, a function
@@ -218,6 +232,6 @@ this release, unless otherwise noted.
 
 --
 exec >/var/tmp/1
-O=v1.5.3-rc1
+O=v1.5.3-rc2
 echo O=`git describe refs/heads/master`
 git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
index 6b6220dfdbd6cacb8a05be4995279bbd0487fada..af5b1558a63219a3eac2570e1cecc97e3008f96f 100644 (file)
@@ -27,7 +27,13 @@ ifdef::backend-docbook[]
 [listingblock]
 <example><title>{title}</title>
 <literallayout>
+ifdef::doctype-manpage[]
+&#10;.ft C&#10;
+endif::doctype-manpage[]
 |
+ifdef::doctype-manpage[]
+&#10;.ft&#10;
+endif::doctype-manpage[]
 </literallayout>
 {title#}</example>
 endif::backend-docbook[]
index bb6b57dc2d00e38fc1d0128fe02fc771a1f83086..bc6aa88417486cda92e8ea47f67bcfc24317b9d2 100644 (file)
@@ -130,8 +130,8 @@ Delete unneeded branch::
 ------------
 $ git clone git://git.kernel.org/.../git.git my.git
 $ cd my.git
-$ git branch -d -r todo html man   <1>
-$ git branch -D test               <2>
+$ git branch -d -r origin/todo origin/html origin/man   <1>
+$ git branch -D test                                    <2>
 ------------
 +
 <1> delete remote-tracking branches "todo", "html", "man"
diff --git a/Documentation/git-p4import.txt b/Documentation/git-p4import.txt
deleted file mode 100644 (file)
index 9967587..0000000
+++ /dev/null
@@ -1,167 +0,0 @@
-git-p4import(1)
-===============
-
-NAME
-----
-git-p4import - Import a Perforce repository into git
-
-
-SYNOPSIS
---------
-[verse]
-`git-p4import` [-q|-v] [--notags] [--authors <file>] [-t <timezone>]
-               <//p4repo/path> <branch>
-`git-p4import` --stitch <//p4repo/path>
-`git-p4import`
-
-
-DESCRIPTION
------------
-Import a Perforce repository into an existing git repository.  When
-a <//p4repo/path> and <branch> are specified a new branch with the
-given name will be created and the initial import will begin.
-
-Once the initial import is complete you can do an incremental import
-of new commits from the Perforce repository.  You do this by checking
-out the appropriate git branch and then running `git-p4import` without
-any options.
-
-The standard p4 client is used to communicate with the Perforce
-repository; it must be configured correctly in order for `git-p4import`
-to operate (see below).
-
-
-OPTIONS
--------
--q::
-       Do not display any progress information.
-
--v::
-        Give extra progress information.
-
-\--authors::
-       Specify an authors file containing a mapping of Perforce user
-       ids to full names and email addresses (see Notes below).
-
-\--notags::
-       Do not create a tag for each imported commit.
-
-\--stitch::
-       Import the contents of the given perforce branch into the
-       currently checked out git branch.
-
-\--log::
-       Store debugging information in the specified file.
-
--t::
-       Specify that the remote repository is in the specified timezone.
-       Timezone must be in the format "US/Pacific" or "Europe/London"
-       etc.  You only need to specify this once, it will be saved in
-       the git config file for the repository.
-
-<//p4repo/path>::
-       The Perforce path that will be imported into the specified branch.
-
-<branch>::
-       The new branch that will be created to hold the Perforce imports.
-
-
-P4 Client
----------
-You must make the `p4` client command available in your $PATH and
-configure it to communicate with the target Perforce repository.
-Typically this means you must set the "$P4PORT" and "$P4CLIENT"
-environment variables.
-
-You must also configure a `p4` client "view" which maps the Perforce
-branch into the top level of your git repository, for example:
-
-------------
-Client: myhost
-
-Root:   /home/sean/import
-
-Options:   noallwrite clobber nocompress unlocked modtime rmdir
-
-View:
-        //public/jam/... //myhost/jam/...
-------------
-
-With the above `p4` client setup, you could import the "jam"
-perforce branch into a branch named "jammy", like so:
-
-------------
-$ mkdir -p /home/sean/import/jam
-$ cd /home/sean/import/jam
-$ git init
-$ git p4import //public/jam jammy
-------------
-
-
-Multiple Branches
------------------
-Note that by creating multiple "views" you can use `git-p4import`
-to import additional branches into the same git repository.
-However, the `p4` client has a limitation in that it silently
-ignores all but the last "view" that maps into the same local
-directory.  So the following will *not* work:
-
-------------
-View:
-        //public/jam/... //myhost/jam/...
-        //public/other/... //myhost/jam/...
-        //public/guest/... //myhost/jam/...
-------------
-
-If you want more than one Perforce branch to be imported into the
-same directory you must employ a workaround.  A simple option is
-to adjust your `p4` client before each import to only include a
-single view.
-
-Another option is to create multiple symlinks locally which all
-point to the same directory in your git repository and then use
-one per "view" instead of listing the actual directory.
-
-
-Tags
-----
-A git tag of the form p4/xx is created for every change imported from
-the Perforce repository where xx is the Perforce changeset number.
-Therefore after the import you can use git to access any commit by its
-Perforce number, e.g. git show p4/327.
-
-The tag associated with the HEAD commit is also how `git-p4import`
-determines if there are new changes to incrementally import from the
-Perforce repository.
-
-If you import from a repository with many thousands of changes
-you will have an equal number of p4/xxxx git tags.  Git tags can
-be expensive in terms of disk space and repository operations.
-If you don't need to perform further incremental imports, you
-may delete the tags.
-
-
-Notes
------
-You can interrupt the import (e.g. ctrl-c) at any time and restart it
-without worry.
-
-Author information is automatically determined by querying the
-Perforce "users" table using the id associated with each change.
-However, if you want to manually supply these mappings you can do
-so with the "--authors" option.  It accepts a file containing a list
-of mappings with each line containing one mapping in the format:
-
-------------
-    perforce_id = Full Name <email@address.com>
-------------
-
-
-Author
-------
-Written by Sean Estabrooks <seanlkml@sympatico.ca>
-
-
-GIT
----
-Part of the gitlink:git[7] suite
index ad95ed9ce149d3db23ba9c36aa7a661e37977cc6..17121ade56c317d974d537fa3aa3c2b5f116e502 100644 (file)
@@ -8,7 +8,8 @@ git-stash - Stash the changes in a dirty working directory away
 SYNOPSIS
 --------
 [verse]
-'git-stash' (save | list | show [<stash>] | apply [<stash>] | clear)
+'git-stash' (list | show [<stash>] | apply [<stash>] | clear)
+'git-stash' [save] [message...]
 
 DESCRIPTION
 -----------
@@ -22,7 +23,9 @@ The modifications stashed away by this command can be listed with
 `git-stash list`, inspected with `git-stash show`, and restored
 (potentially on top of a different commit) with `git-stash apply`.
 Calling git-stash without any arguments is equivalent to `git-stash
-save`.
+save`.  A stash is by default listed as "WIP on 'branchname' ...", but
+you can give a more descriptive message on the command line when
+you create one.
 
 The latest stash you created is stored in `$GIT_DIR/refs/stash`; older
 stashes are found in the reflog of this reference and can be named using
@@ -48,8 +51,8 @@ list::
        based on.
 +
 ----------------------------------------------------------------
-stash@{0}: submit: 6ebd0e2... Add git-stash
-stash@{1}: master: 9cc0589... Merge branch 'master' of gfi
+stash@{0}: WIP on submit: 6ebd0e2... Update git-stash documentation
+stash@{1}: On master: 9cc0589... Add git-stash
 ----------------------------------------------------------------
 
 show [<stash>]::
diff --git a/INSTALL b/INSTALL
index 63ba1480a697ddeb98c5665106352fd2bb0ceb5a..79e71b6922cd82f411dbec1c77f6cf99c1b2ea34 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -56,9 +56,8 @@ Issues of note:
 
        - "zlib", the compression library. Git won't build without it.
 
-       - "openssl".  The git-rev-list program uses bignum support from
-         openssl, and unless you specify otherwise, you'll also get the
-         SHA1 library from here.
+       - "openssl".  Unless you specify otherwise, you'll get the SHA1
+         library from here.
 
          If you don't have openssl, you can use one of the SHA1 libraries
          that come with git (git includes the one from Mozilla, and has
@@ -73,7 +72,7 @@ Issues of note:
          management over DAV.  Similar to "curl" above, this is optional.
 
         - "wish", the Tcl/Tk windowing shell is used in gitk to show the
-          history graphically
+          history graphically, and in git-gui.
 
        - "ssh" is used to push and pull over the net
 
index 5db3fb4ce13a62aec54d878406899903454fa45a..73b487fba9dc699fd8d2061113b8e94b3d4fa698 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -112,8 +112,6 @@ all::
 # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
 # MakeMaker (e.g. using ActiveState under Cygwin).
 #
-# Define WITH_P4IMPORT to build and install Python git-p4import script.
-#
 # Define NO_TCLTK if you do not want Tcl/Tk GUI.
 #
 # The TCL_PATH variable governs the location of the Tcl interpreter
@@ -223,20 +221,9 @@ SCRIPT_PERL = \
        git-svnimport.perl git-cvsexportcommit.perl \
        git-send-email.perl git-svn.perl
 
-SCRIPT_PYTHON = \
-       git-p4import.py
-
-ifdef WITH_P4IMPORT
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
-         $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
          git-status git-instaweb
-else
-SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
-         $(patsubst %.perl,%,$(SCRIPT_PERL)) \
-         git-status git-instaweb
-endif
-
 
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS = \
@@ -286,9 +273,6 @@ endif
 ifndef PERL_PATH
        PERL_PATH = /usr/bin/perl
 endif
-ifndef PYTHON_PATH
-       PYTHON_PATH = /usr/local/bin/python
-endif
 
 export PERL_PATH
 
@@ -711,7 +695,6 @@ prefix_SQ = $(subst ','\'',$(prefix))
 
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
-PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
@@ -782,15 +765,6 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
 
 $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
 
-$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
-       $(RM) $@ $@+
-       sed -e '1s|#!.*/python|#!$(PYTHON_PATH_SQ)|' \
-           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-           -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
-           $@.py >$@+
-       chmod +x $@+
-       mv $@+ $@
-
 perl/perl.mak: GIT-CFLAGS
        $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
 
index 489c2c58c01514ac3d967d1c3f46f1243f853580..a37a4fff39afe6bafaace59b710e1dfdc56bf35a 100644 (file)
@@ -499,15 +499,40 @@ static int decode_b_segment(char *in, char *ot, char *ep)
        return 0;
 }
 
+/*
+ * When there is no known charset, guess.
+ *
+ * Right now we assume that if the target is UTF-8 (the default),
+ * and it already looks like UTF-8 (which includes US-ASCII as its
+ * subset, of course) then that is what it is and there is nothing
+ * to do.
+ *
+ * Otherwise, we default to assuming it is Latin1 for historical
+ * reasons.
+ */
+static const char *guess_charset(const char *line, const char *target_charset)
+{
+       if (is_encoding_utf8(target_charset)) {
+               if (is_utf8(line))
+                       return NULL;
+       }
+       return "latin1";
+}
+
 static void convert_to_utf8(char *line, const char *charset)
 {
-       static const char latin_one[] = "latin1";
-       const char *input_charset = *charset ? charset : latin_one;
-       char *out = reencode_string(line, metainfo_charset, input_charset);
+       char *out;
+
+       if (!charset || !*charset) {
+               charset = guess_charset(line, metainfo_charset);
+               if (!charset)
+                       return;
+       }
 
+       out = reencode_string(line, metainfo_charset, charset);
        if (!out)
                die("cannot convert from %s to %s\n",
-                   input_charset, metainfo_charset);
+                   charset, metainfo_charset);
        strcpy(line, out);
        free(out);
 }
index 5e94d6fcd35f8354a326cdb82e2f7e6b1c26f26b..a48540a92b4aa5140a87469b36c1f9b3d8e46e7f 100644 (file)
@@ -13,7 +13,7 @@ all: $(ELC)
 
 install: all
        $(INSTALL) -d $(DESTDIR)$(emacsdir)
-       $(INSTALL_ELC) $(ELC) $(DESTDIR)$(emacsdir)
+       $(INSTALL_ELC) $(ELC:.elc=.el) $(ELC) $(DESTDIR)$(emacsdir)
 
 %.elc: %.el
        $(EMACS) -batch -f batch-byte-compile $<
diff --git a/contrib/p4import/README b/contrib/p4import/README
new file mode 100644 (file)
index 0000000..b9892b6
--- /dev/null
@@ -0,0 +1 @@
+Please see contrib/fast-import/git-p4 for a better Perforce importer.
diff --git a/contrib/p4import/git-p4import.py b/contrib/p4import/git-p4import.py
new file mode 100644 (file)
index 0000000..0f3d97b
--- /dev/null
@@ -0,0 +1,360 @@
+#!/usr/bin/python
+#
+# This tool is copyright (c) 2006, Sean Estabrooks.
+# It is released under the Gnu Public License, version 2.
+#
+# Import Perforce branches into Git repositories.
+# Checking out the files is done by calling the standard p4
+# client which you must have properly configured yourself
+#
+
+import marshal
+import os
+import sys
+import time
+import getopt
+
+from signal import signal, \
+   SIGPIPE, SIGINT, SIG_DFL, \
+   default_int_handler
+
+signal(SIGPIPE, SIG_DFL)
+s = signal(SIGINT, SIG_DFL)
+if s != default_int_handler:
+   signal(SIGINT, s)
+
+def die(msg, *args):
+    for a in args:
+        msg = "%s %s" % (msg, a)
+    print "git-p4import fatal error:", msg
+    sys.exit(1)
+
+def usage():
+    print "USAGE: git-p4import [-q|-v]  [--authors=<file>]  [-t <timezone>]  [//p4repo/path <branch>]"
+    sys.exit(1)
+
+verbosity = 1
+logfile = "/dev/null"
+ignore_warnings = False
+stitch = 0
+tagall = True
+
+def report(level, msg, *args):
+    global verbosity
+    global logfile
+    for a in args:
+        msg = "%s %s" % (msg, a)
+    fd = open(logfile, "a")
+    fd.writelines(msg)
+    fd.close()
+    if level <= verbosity:
+        print msg
+
+class p4_command:
+    def __init__(self, _repopath):
+        try:
+            global logfile
+            self.userlist = {}
+            if _repopath[-1] == '/':
+                self.repopath = _repopath[:-1]
+            else:
+                self.repopath = _repopath
+            if self.repopath[-4:] != "/...":
+                self.repopath= "%s/..." % self.repopath
+            f=os.popen('p4 -V 2>>%s'%logfile, 'rb')
+            a = f.readlines()
+            if f.close():
+                raise
+        except:
+                die("Could not find the \"p4\" command")
+
+    def p4(self, cmd, *args):
+        global logfile
+        cmd = "%s %s" % (cmd, ' '.join(args))
+        report(2, "P4:", cmd)
+        f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb')
+        list = []
+        while 1:
+           try:
+                list.append(marshal.load(f))
+           except EOFError:
+                break
+        self.ret = f.close()
+        return list
+
+    def sync(self, id, force=False, trick=False, test=False):
+        if force:
+            ret = self.p4("sync -f %s@%s"%(self.repopath, id))[0]
+        elif trick:
+            ret = self.p4("sync -k %s@%s"%(self.repopath, id))[0]
+        elif test:
+            ret = self.p4("sync -n %s@%s"%(self.repopath, id))[0]
+        else:
+            ret = self.p4("sync    %s@%s"%(self.repopath, id))[0]
+        if ret['code'] == "error":
+             data = ret['data'].upper()
+             if data.find('VIEW') > 0:
+                 die("Perforce reports %s is not in client view"% self.repopath)
+             elif data.find('UP-TO-DATE') < 0:
+                 die("Could not sync files from perforce", self.repopath)
+
+    def changes(self, since=0):
+        try:
+            list = []
+            for rec in self.p4("changes %s@%s,#head" % (self.repopath, since+1)):
+                list.append(rec['change'])
+            list.reverse()
+            return list
+        except:
+            return []
+
+    def authors(self, filename):
+        f=open(filename)
+        for l in f.readlines():
+            self.userlist[l[:l.find('=')].rstrip()] = \
+                    (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')])
+        f.close()
+        for f,e in self.userlist.items():
+                report(2, f, ":", e[0], "  <", e[1], ">")
+
+    def _get_user(self, id):
+        if not self.userlist.has_key(id):
+            try:
+                user = self.p4("users", id)[0]
+                self.userlist[id] = (user['FullName'], user['Email'])
+            except:
+                self.userlist[id] = (id, "")
+        return self.userlist[id]
+
+    def _format_date(self, ticks):
+        symbol='+'
+        name = time.tzname[0]
+        offset = time.timezone
+        if ticks[8]:
+            name = time.tzname[1]
+            offset = time.altzone
+        if offset < 0:
+            offset *= -1
+            symbol = '-'
+        localo = "%s%02d%02d %s" % (symbol, offset / 3600, offset % 3600, name)
+        tickso = time.strftime("%a %b %d %H:%M:%S %Y", ticks)
+        return "%s %s" % (tickso, localo)
+
+    def where(self):
+        try:
+            return self.p4("where %s" % self.repopath)[-1]['path']
+        except:
+            return ""
+
+    def describe(self, num):
+        desc = self.p4("describe -s", num)[0]
+        self.msg = desc['desc']
+        self.author, self.email = self._get_user(desc['user'])
+        self.date = self._format_date(time.localtime(long(desc['time'])))
+        return self
+
+class git_command:
+    def __init__(self):
+        try:
+            self.version = self.git("--version")[0][12:].rstrip()
+        except:
+            die("Could not find the \"git\" command")
+        try:
+            self.gitdir = self.get_single("rev-parse --git-dir")
+            report(2, "gdir:", self.gitdir)
+        except:
+            die("Not a git repository... did you forget to \"git init\" ?")
+        try:
+            self.cdup = self.get_single("rev-parse --show-cdup")
+            if self.cdup != "":
+                os.chdir(self.cdup)
+            self.topdir = os.getcwd()
+            report(2, "topdir:", self.topdir)
+        except:
+            die("Could not find top git directory")
+
+    def git(self, cmd):
+        global logfile
+        report(2, "GIT:", cmd)
+        f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb')
+        r=f.readlines()
+        self.ret = f.close()
+        return r
+
+    def get_single(self, cmd):
+        return self.git(cmd)[0].rstrip()
+
+    def current_branch(self):
+        try:
+            testit = self.git("rev-parse --verify HEAD")[0]
+            return self.git("symbolic-ref HEAD")[0][11:].rstrip()
+        except:
+            return None
+
+    def get_config(self, variable):
+        try:
+            return self.git("config --get %s" % variable)[0].rstrip()
+        except:
+            return None
+
+    def set_config(self, variable, value):
+        try:
+            self.git("config %s %s"%(variable, value) )
+        except:
+            die("Could not set %s to " % variable, value)
+
+    def make_tag(self, name, head):
+        self.git("tag -f %s %s"%(name,head))
+
+    def top_change(self, branch):
+        try:
+            a=self.get_single("name-rev --tags refs/heads/%s" % branch)
+            loc = a.find(' tags/') + 6
+            if a[loc:loc+3] != "p4/":
+                raise
+            return int(a[loc+3:][:-2])
+        except:
+            return 0
+
+    def update_index(self):
+        self.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin")
+
+    def checkout(self, branch):
+        self.git("checkout %s" % branch)
+
+    def repoint_head(self, branch):
+        self.git("symbolic-ref HEAD refs/heads/%s" % branch)
+
+    def remove_files(self):
+        self.git("ls-files | xargs rm")
+
+    def clean_directories(self):
+        self.git("clean -d")
+
+    def fresh_branch(self, branch):
+        report(1, "Creating new branch", branch)
+        self.git("ls-files | xargs rm")
+        os.remove(".git/index")
+        self.repoint_head(branch)
+        self.git("clean -d")
+
+    def basedir(self):
+        return self.topdir
+
+    def commit(self, author, email, date, msg, id):
+        self.update_index()
+        fd=open(".msg", "w")
+        fd.writelines(msg)
+        fd.close()
+        try:
+                current = self.get_single("rev-parse --verify HEAD")
+                head = "-p HEAD"
+        except:
+                current = ""
+                head = ""
+        tree = self.get_single("write-tree")
+        for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]:
+            os.environ['GIT_AUTHOR_%s'%r] = l
+            os.environ['GIT_COMMITTER_%s'%r] = l
+        commit = self.get_single("commit-tree %s %s < .msg" % (tree,head))
+        os.remove(".msg")
+        self.make_tag("p4/%s"%id, commit)
+        self.git("update-ref HEAD %s %s" % (commit, current) )
+
+try:
+    opts, args = getopt.getopt(sys.argv[1:], "qhvt:",
+            ["authors=","help","stitch=","timezone=","log=","ignore","notags"])
+except getopt.GetoptError:
+    usage()
+
+for o, a in opts:
+    if o == "-q":
+        verbosity = 0
+    if o == "-v":
+        verbosity += 1
+    if o in ("--log"):
+        logfile = a
+    if o in ("--notags"):
+        tagall = False
+    if o in ("-h", "--help"):
+        usage()
+    if o in ("--ignore"):
+        ignore_warnings = True
+
+git = git_command()
+branch=git.current_branch()
+
+for o, a in opts:
+    if o in ("-t", "--timezone"):
+        git.set_config("perforce.timezone", a)
+    if o in ("--stitch"):
+        git.set_config("perforce.%s.path" % branch, a)
+        stitch = 1
+
+if len(args) == 2:
+    branch = args[1]
+    git.checkout(branch)
+    if branch == git.current_branch():
+        die("Branch %s already exists!" % branch)
+    report(1, "Setting perforce to ", args[0])
+    git.set_config("perforce.%s.path" % branch, args[0])
+elif len(args) != 0:
+    die("You must specify the perforce //depot/path and git branch")
+
+p4path = git.get_config("perforce.%s.path" % branch)
+if p4path == None:
+    die("Do not know Perforce //depot/path for git branch", branch)
+
+p4 = p4_command(p4path)
+
+for o, a in opts:
+    if o in ("-a", "--authors"):
+        p4.authors(a)
+
+localdir = git.basedir()
+if p4.where()[:len(localdir)] != localdir:
+    report(1, "**WARNING** Appears p4 client is misconfigured")
+    report(1, "   for sync from %s to %s" % (p4.repopath, localdir))
+    if ignore_warnings != True:
+        die("Reconfigure or use \"--ignore\" on command line")
+
+if stitch == 0:
+    top = git.top_change(branch)
+else:
+    top = 0
+changes = p4.changes(top)
+count = len(changes)
+if count == 0:
+    report(1, "Already up to date...")
+    sys.exit(0)
+
+ptz = git.get_config("perforce.timezone")
+if ptz:
+    report(1, "Setting timezone to", ptz)
+    os.environ['TZ'] = ptz
+    time.tzset()
+
+if stitch == 1:
+    git.remove_files()
+    git.clean_directories()
+    p4.sync(changes[0], force=True)
+elif top == 0 and branch != git.current_branch():
+    p4.sync(changes[0], test=True)
+    report(1, "Creating new initial commit");
+    git.fresh_branch(branch)
+    p4.sync(changes[0], force=True)
+else:
+    p4.sync(changes[0], trick=True)
+
+report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch))
+for id in changes:
+    report(1, "Importing changeset", id)
+    change = p4.describe(id)
+    p4.sync(id)
+    if tagall :
+            git.commit(change.author, change.email, change.date, change.msg, id)
+    else:
+            git.commit(change.author, change.email, change.date, change.msg, "import")
+    if stitch == 1:
+        git.clean_directories()
+        stitch = 0
diff --git a/contrib/p4import/git-p4import.txt b/contrib/p4import/git-p4import.txt
new file mode 100644 (file)
index 0000000..9967587
--- /dev/null
@@ -0,0 +1,167 @@
+git-p4import(1)
+===============
+
+NAME
+----
+git-p4import - Import a Perforce repository into git
+
+
+SYNOPSIS
+--------
+[verse]
+`git-p4import` [-q|-v] [--notags] [--authors <file>] [-t <timezone>]
+               <//p4repo/path> <branch>
+`git-p4import` --stitch <//p4repo/path>
+`git-p4import`
+
+
+DESCRIPTION
+-----------
+Import a Perforce repository into an existing git repository.  When
+a <//p4repo/path> and <branch> are specified a new branch with the
+given name will be created and the initial import will begin.
+
+Once the initial import is complete you can do an incremental import
+of new commits from the Perforce repository.  You do this by checking
+out the appropriate git branch and then running `git-p4import` without
+any options.
+
+The standard p4 client is used to communicate with the Perforce
+repository; it must be configured correctly in order for `git-p4import`
+to operate (see below).
+
+
+OPTIONS
+-------
+-q::
+       Do not display any progress information.
+
+-v::
+        Give extra progress information.
+
+\--authors::
+       Specify an authors file containing a mapping of Perforce user
+       ids to full names and email addresses (see Notes below).
+
+\--notags::
+       Do not create a tag for each imported commit.
+
+\--stitch::
+       Import the contents of the given perforce branch into the
+       currently checked out git branch.
+
+\--log::
+       Store debugging information in the specified file.
+
+-t::
+       Specify that the remote repository is in the specified timezone.
+       Timezone must be in the format "US/Pacific" or "Europe/London"
+       etc.  You only need to specify this once, it will be saved in
+       the git config file for the repository.
+
+<//p4repo/path>::
+       The Perforce path that will be imported into the specified branch.
+
+<branch>::
+       The new branch that will be created to hold the Perforce imports.
+
+
+P4 Client
+---------
+You must make the `p4` client command available in your $PATH and
+configure it to communicate with the target Perforce repository.
+Typically this means you must set the "$P4PORT" and "$P4CLIENT"
+environment variables.
+
+You must also configure a `p4` client "view" which maps the Perforce
+branch into the top level of your git repository, for example:
+
+------------
+Client: myhost
+
+Root:   /home/sean/import
+
+Options:   noallwrite clobber nocompress unlocked modtime rmdir
+
+View:
+        //public/jam/... //myhost/jam/...
+------------
+
+With the above `p4` client setup, you could import the "jam"
+perforce branch into a branch named "jammy", like so:
+
+------------
+$ mkdir -p /home/sean/import/jam
+$ cd /home/sean/import/jam
+$ git init
+$ git p4import //public/jam jammy
+------------
+
+
+Multiple Branches
+-----------------
+Note that by creating multiple "views" you can use `git-p4import`
+to import additional branches into the same git repository.
+However, the `p4` client has a limitation in that it silently
+ignores all but the last "view" that maps into the same local
+directory.  So the following will *not* work:
+
+------------
+View:
+        //public/jam/... //myhost/jam/...
+        //public/other/... //myhost/jam/...
+        //public/guest/... //myhost/jam/...
+------------
+
+If you want more than one Perforce branch to be imported into the
+same directory you must employ a workaround.  A simple option is
+to adjust your `p4` client before each import to only include a
+single view.
+
+Another option is to create multiple symlinks locally which all
+point to the same directory in your git repository and then use
+one per "view" instead of listing the actual directory.
+
+
+Tags
+----
+A git tag of the form p4/xx is created for every change imported from
+the Perforce repository where xx is the Perforce changeset number.
+Therefore after the import you can use git to access any commit by its
+Perforce number, e.g. git show p4/327.
+
+The tag associated with the HEAD commit is also how `git-p4import`
+determines if there are new changes to incrementally import from the
+Perforce repository.
+
+If you import from a repository with many thousands of changes
+you will have an equal number of p4/xxxx git tags.  Git tags can
+be expensive in terms of disk space and repository operations.
+If you don't need to perform further incremental imports, you
+may delete the tags.
+
+
+Notes
+-----
+You can interrupt the import (e.g. ctrl-c) at any time and restart it
+without worry.
+
+Author information is automatically determined by querying the
+Perforce "users" table using the id associated with each change.
+However, if you want to manually supply these mappings you can do
+so with the "--authors" option.  It accepts a file containing a list
+of mappings with each line containing one mapping in the format:
+
+------------
+    perforce_id = Full Name <email@address.com>
+------------
+
+
+Author
+------
+Written by Sean Estabrooks <seanlkml@sympatico.ca>
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/entry.c b/entry.c
index c540ae13e858685faa8dbbada5b064074235ae6d..0625112339deee7418571b5d513f923d6f00092b 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -8,17 +8,40 @@ static void create_directories(const char *path, const struct checkout *state)
        const char *slash = path;
 
        while ((slash = strchr(slash+1, '/')) != NULL) {
+               struct stat st;
+               int stat_status;
+
                len = slash - path;
                memcpy(buf, path, len);
                buf[len] = 0;
+
+               if (len <= state->base_dir_len)
+                       /*
+                        * checkout-index --prefix=<dir>; <dir> is
+                        * allowed to be a symlink to an existing
+                        * directory.
+                        */
+                       stat_status = stat(buf, &st);
+               else
+                       /*
+                        * if there currently is a symlink, we would
+                        * want to replace it with a real directory.
+                        */
+                       stat_status = lstat(buf, &st);
+
+               if (!stat_status && S_ISDIR(st.st_mode))
+                       continue; /* ok, it is already a directory. */
+
+               /*
+                * We know stat_status == 0 means something exists
+                * there and this mkdir would fail, but that is an
+                * error codepath; we do not care, as we unlink and
+                * mkdir again in such a case.
+                */
                if (mkdir(buf, 0777)) {
-                       if (errno == EEXIST) {
-                               struct stat st;
-                               if (len > state->base_dir_len && state->force && !unlink(buf) && !mkdir(buf, 0777))
-                                       continue;
-                               if (!stat(buf, &st) && S_ISDIR(st.st_mode))
-                                       continue; /* ok */
-                       }
+                       if (errno == EEXIST && state->force &&
+                           !unlink(buf) && !mkdir(buf, 0777))
+                               continue;
                        die("cannot create directory at %s", buf);
                }
        }
index 54019706dc6e7851233dd05156d0c923ef09a014..0d000ed306caad7be70856c070a362f09439e48d 100755 (executable)
@@ -8,8 +8,6 @@
 # a new branch. You can specify a number of filters to modify the commits,
 # files and trees.
 
-set -e
-
 USAGE="git-filter-branch [-d TEMPDIR] [FILTERS] DESTBRANCH [REV-RANGE]"
 . git-sh-setup
 
@@ -141,9 +139,10 @@ git show-ref "refs/heads/$dstbranch" 2> /dev/null &&
        die "branch $dstbranch already exists"
 
 test ! -e "$tempdir" || die "$tempdir already exists, please remove it"
-mkdir -p "$tempdir/t"
-cd "$tempdir/t"
-workdir="$(pwd)"
+mkdir -p "$tempdir/t" &&
+cd "$tempdir/t" &&
+workdir="$(pwd)" ||
+die ""
 
 case "$GIT_DIR" in
 /*)
@@ -155,12 +154,12 @@ esac
 export GIT_DIR GIT_WORK_TREE=.
 
 export GIT_INDEX_FILE="$(pwd)/../index"
-git read-tree # seed the index file
+git read-tree || die "Could not seed the index"
 
 ret=0
 
-
-mkdir ../map # map old->new commit ids for rewriting parents
+# map old->new commit ids for rewriting parents
+mkdir ../map || die "Could not create map/ directory"
 
 case "$filter_subdir" in
 "")
@@ -170,7 +169,7 @@ case "$filter_subdir" in
 *)
        git rev-list --reverse --topo-order --default HEAD \
                --parents --full-history "$@" -- "$filter_subdir"
-esac > ../revs
+esac > ../revs || die "Could not get the commits"
 commits=$(wc -l <../revs | tr -d " ")
 
 test $commits -eq 0 && die "Found nothing to rewrite"
@@ -186,10 +185,11 @@ while read commit parents; do
                ;;
        *)
                git read-tree -i -m $commit:"$filter_subdir"
-       esac
+       esac || die "Could not initialize the index"
 
        export GIT_COMMIT=$commit
-       git cat-file commit "$commit" >../commit
+       git cat-file commit "$commit" >../commit ||
+               die "Cannot read commit $commit"
 
        eval "$(set_ident AUTHOR <../commit)" ||
                die "setting author failed for commit $commit"
@@ -199,7 +199,8 @@ while read commit parents; do
                die "env filter failed: $filter_env"
 
        if [ "$filter_tree" ]; then
-               git checkout-index -f -u -a
+               git checkout-index -f -u -a ||
+                       die "Could not checkout the index"
                # files that $commit removed are now still in the working tree;
                # remove them, else they would be added again
                git ls-files -z --others | xargs -0 rm -f
@@ -240,7 +241,8 @@ case "$target_head" in
        echo Nothing rewritten
        ;;
 *)
-       git update-ref refs/heads/"$dstbranch" $target_head
+       git update-ref refs/heads/"$dstbranch" $target_head ||
+               die "Could not update $dstbranch with $target_head"
        if [ $(wc -l <../map/$src_head) -gt 1 ]; then
                echo "WARNING: Your commit filter caused the head commit to expand to several rewritten commits. Only the first such commit was recorded as the current $dstbranch head but you will need to resolve the situation now (probably by manually merging the other commits). These are all the commits:" >&2
                sed 's/^/       /' ../map/$src_head >&2
@@ -277,7 +279,8 @@ if [ "$filter_tag_name" ]; then
                        warn "unreferencing tag object $sha1t"
                fi
 
-               git update-ref "refs/tags/$new_ref" "$new_sha1"
+               git update-ref "refs/tags/$new_ref" "$new_sha1" ||
+                       die "Could not write tag $new_ref"
        done
 fi
 
diff --git a/git-p4import.py b/git-p4import.py
deleted file mode 100644 (file)
index 0f3d97b..0000000
+++ /dev/null
@@ -1,360 +0,0 @@
-#!/usr/bin/python
-#
-# This tool is copyright (c) 2006, Sean Estabrooks.
-# It is released under the Gnu Public License, version 2.
-#
-# Import Perforce branches into Git repositories.
-# Checking out the files is done by calling the standard p4
-# client which you must have properly configured yourself
-#
-
-import marshal
-import os
-import sys
-import time
-import getopt
-
-from signal import signal, \
-   SIGPIPE, SIGINT, SIG_DFL, \
-   default_int_handler
-
-signal(SIGPIPE, SIG_DFL)
-s = signal(SIGINT, SIG_DFL)
-if s != default_int_handler:
-   signal(SIGINT, s)
-
-def die(msg, *args):
-    for a in args:
-        msg = "%s %s" % (msg, a)
-    print "git-p4import fatal error:", msg
-    sys.exit(1)
-
-def usage():
-    print "USAGE: git-p4import [-q|-v]  [--authors=<file>]  [-t <timezone>]  [//p4repo/path <branch>]"
-    sys.exit(1)
-
-verbosity = 1
-logfile = "/dev/null"
-ignore_warnings = False
-stitch = 0
-tagall = True
-
-def report(level, msg, *args):
-    global verbosity
-    global logfile
-    for a in args:
-        msg = "%s %s" % (msg, a)
-    fd = open(logfile, "a")
-    fd.writelines(msg)
-    fd.close()
-    if level <= verbosity:
-        print msg
-
-class p4_command:
-    def __init__(self, _repopath):
-        try:
-            global logfile
-            self.userlist = {}
-            if _repopath[-1] == '/':
-                self.repopath = _repopath[:-1]
-            else:
-                self.repopath = _repopath
-            if self.repopath[-4:] != "/...":
-                self.repopath= "%s/..." % self.repopath
-            f=os.popen('p4 -V 2>>%s'%logfile, 'rb')
-            a = f.readlines()
-            if f.close():
-                raise
-        except:
-                die("Could not find the \"p4\" command")
-
-    def p4(self, cmd, *args):
-        global logfile
-        cmd = "%s %s" % (cmd, ' '.join(args))
-        report(2, "P4:", cmd)
-        f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb')
-        list = []
-        while 1:
-           try:
-                list.append(marshal.load(f))
-           except EOFError:
-                break
-        self.ret = f.close()
-        return list
-
-    def sync(self, id, force=False, trick=False, test=False):
-        if force:
-            ret = self.p4("sync -f %s@%s"%(self.repopath, id))[0]
-        elif trick:
-            ret = self.p4("sync -k %s@%s"%(self.repopath, id))[0]
-        elif test:
-            ret = self.p4("sync -n %s@%s"%(self.repopath, id))[0]
-        else:
-            ret = self.p4("sync    %s@%s"%(self.repopath, id))[0]
-        if ret['code'] == "error":
-             data = ret['data'].upper()
-             if data.find('VIEW') > 0:
-                 die("Perforce reports %s is not in client view"% self.repopath)
-             elif data.find('UP-TO-DATE') < 0:
-                 die("Could not sync files from perforce", self.repopath)
-
-    def changes(self, since=0):
-        try:
-            list = []
-            for rec in self.p4("changes %s@%s,#head" % (self.repopath, since+1)):
-                list.append(rec['change'])
-            list.reverse()
-            return list
-        except:
-            return []
-
-    def authors(self, filename):
-        f=open(filename)
-        for l in f.readlines():
-            self.userlist[l[:l.find('=')].rstrip()] = \
-                    (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')])
-        f.close()
-        for f,e in self.userlist.items():
-                report(2, f, ":", e[0], "  <", e[1], ">")
-
-    def _get_user(self, id):
-        if not self.userlist.has_key(id):
-            try:
-                user = self.p4("users", id)[0]
-                self.userlist[id] = (user['FullName'], user['Email'])
-            except:
-                self.userlist[id] = (id, "")
-        return self.userlist[id]
-
-    def _format_date(self, ticks):
-        symbol='+'
-        name = time.tzname[0]
-        offset = time.timezone
-        if ticks[8]:
-            name = time.tzname[1]
-            offset = time.altzone
-        if offset < 0:
-            offset *= -1
-            symbol = '-'
-        localo = "%s%02d%02d %s" % (symbol, offset / 3600, offset % 3600, name)
-        tickso = time.strftime("%a %b %d %H:%M:%S %Y", ticks)
-        return "%s %s" % (tickso, localo)
-
-    def where(self):
-        try:
-            return self.p4("where %s" % self.repopath)[-1]['path']
-        except:
-            return ""
-
-    def describe(self, num):
-        desc = self.p4("describe -s", num)[0]
-        self.msg = desc['desc']
-        self.author, self.email = self._get_user(desc['user'])
-        self.date = self._format_date(time.localtime(long(desc['time'])))
-        return self
-
-class git_command:
-    def __init__(self):
-        try:
-            self.version = self.git("--version")[0][12:].rstrip()
-        except:
-            die("Could not find the \"git\" command")
-        try:
-            self.gitdir = self.get_single("rev-parse --git-dir")
-            report(2, "gdir:", self.gitdir)
-        except:
-            die("Not a git repository... did you forget to \"git init\" ?")
-        try:
-            self.cdup = self.get_single("rev-parse --show-cdup")
-            if self.cdup != "":
-                os.chdir(self.cdup)
-            self.topdir = os.getcwd()
-            report(2, "topdir:", self.topdir)
-        except:
-            die("Could not find top git directory")
-
-    def git(self, cmd):
-        global logfile
-        report(2, "GIT:", cmd)
-        f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb')
-        r=f.readlines()
-        self.ret = f.close()
-        return r
-
-    def get_single(self, cmd):
-        return self.git(cmd)[0].rstrip()
-
-    def current_branch(self):
-        try:
-            testit = self.git("rev-parse --verify HEAD")[0]
-            return self.git("symbolic-ref HEAD")[0][11:].rstrip()
-        except:
-            return None
-
-    def get_config(self, variable):
-        try:
-            return self.git("config --get %s" % variable)[0].rstrip()
-        except:
-            return None
-
-    def set_config(self, variable, value):
-        try:
-            self.git("config %s %s"%(variable, value) )
-        except:
-            die("Could not set %s to " % variable, value)
-
-    def make_tag(self, name, head):
-        self.git("tag -f %s %s"%(name,head))
-
-    def top_change(self, branch):
-        try:
-            a=self.get_single("name-rev --tags refs/heads/%s" % branch)
-            loc = a.find(' tags/') + 6
-            if a[loc:loc+3] != "p4/":
-                raise
-            return int(a[loc+3:][:-2])
-        except:
-            return 0
-
-    def update_index(self):
-        self.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin")
-
-    def checkout(self, branch):
-        self.git("checkout %s" % branch)
-
-    def repoint_head(self, branch):
-        self.git("symbolic-ref HEAD refs/heads/%s" % branch)
-
-    def remove_files(self):
-        self.git("ls-files | xargs rm")
-
-    def clean_directories(self):
-        self.git("clean -d")
-
-    def fresh_branch(self, branch):
-        report(1, "Creating new branch", branch)
-        self.git("ls-files | xargs rm")
-        os.remove(".git/index")
-        self.repoint_head(branch)
-        self.git("clean -d")
-
-    def basedir(self):
-        return self.topdir
-
-    def commit(self, author, email, date, msg, id):
-        self.update_index()
-        fd=open(".msg", "w")
-        fd.writelines(msg)
-        fd.close()
-        try:
-                current = self.get_single("rev-parse --verify HEAD")
-                head = "-p HEAD"
-        except:
-                current = ""
-                head = ""
-        tree = self.get_single("write-tree")
-        for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]:
-            os.environ['GIT_AUTHOR_%s'%r] = l
-            os.environ['GIT_COMMITTER_%s'%r] = l
-        commit = self.get_single("commit-tree %s %s < .msg" % (tree,head))
-        os.remove(".msg")
-        self.make_tag("p4/%s"%id, commit)
-        self.git("update-ref HEAD %s %s" % (commit, current) )
-
-try:
-    opts, args = getopt.getopt(sys.argv[1:], "qhvt:",
-            ["authors=","help","stitch=","timezone=","log=","ignore","notags"])
-except getopt.GetoptError:
-    usage()
-
-for o, a in opts:
-    if o == "-q":
-        verbosity = 0
-    if o == "-v":
-        verbosity += 1
-    if o in ("--log"):
-        logfile = a
-    if o in ("--notags"):
-        tagall = False
-    if o in ("-h", "--help"):
-        usage()
-    if o in ("--ignore"):
-        ignore_warnings = True
-
-git = git_command()
-branch=git.current_branch()
-
-for o, a in opts:
-    if o in ("-t", "--timezone"):
-        git.set_config("perforce.timezone", a)
-    if o in ("--stitch"):
-        git.set_config("perforce.%s.path" % branch, a)
-        stitch = 1
-
-if len(args) == 2:
-    branch = args[1]
-    git.checkout(branch)
-    if branch == git.current_branch():
-        die("Branch %s already exists!" % branch)
-    report(1, "Setting perforce to ", args[0])
-    git.set_config("perforce.%s.path" % branch, args[0])
-elif len(args) != 0:
-    die("You must specify the perforce //depot/path and git branch")
-
-p4path = git.get_config("perforce.%s.path" % branch)
-if p4path == None:
-    die("Do not know Perforce //depot/path for git branch", branch)
-
-p4 = p4_command(p4path)
-
-for o, a in opts:
-    if o in ("-a", "--authors"):
-        p4.authors(a)
-
-localdir = git.basedir()
-if p4.where()[:len(localdir)] != localdir:
-    report(1, "**WARNING** Appears p4 client is misconfigured")
-    report(1, "   for sync from %s to %s" % (p4.repopath, localdir))
-    if ignore_warnings != True:
-        die("Reconfigure or use \"--ignore\" on command line")
-
-if stitch == 0:
-    top = git.top_change(branch)
-else:
-    top = 0
-changes = p4.changes(top)
-count = len(changes)
-if count == 0:
-    report(1, "Already up to date...")
-    sys.exit(0)
-
-ptz = git.get_config("perforce.timezone")
-if ptz:
-    report(1, "Setting timezone to", ptz)
-    os.environ['TZ'] = ptz
-    time.tzset()
-
-if stitch == 1:
-    git.remove_files()
-    git.clean_directories()
-    p4.sync(changes[0], force=True)
-elif top == 0 and branch != git.current_branch():
-    p4.sync(changes[0], test=True)
-    report(1, "Creating new initial commit");
-    git.fresh_branch(branch)
-    p4.sync(changes[0], force=True)
-else:
-    p4.sync(changes[0], trick=True)
-
-report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch))
-for id in changes:
-    report(1, "Importing changeset", id)
-    change = p4.describe(id)
-    p4.sync(id)
-    if tagall :
-            git.commit(change.author, change.email, change.date, change.msg, id)
-    else:
-            git.commit(change.author, change.email, change.date, change.msg, "import")
-    if stitch == 1:
-        git.clean_directories()
-        stitch = 0
index 299b40f93829eeef982b04f7f53dbb2d6baf9a6a..6c692a79e778a74a484aa4f5364aabaa8619057c 100755 (executable)
@@ -740,7 +740,7 @@ sub load_authors {
        my $log = $cmd eq 'log';
        while (<$authors>) {
                chomp;
-               next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/;
+               next unless /^(.+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/;
                my ($user, $name, $email) = ($1, $2, $3);
                if ($log) {
                        $Git::SVN::Log::rusers{"$name <$email>"} = $user;
@@ -2724,6 +2724,9 @@ sub repo_path {
 
 sub url_path {
        my ($self, $path) = @_;
+       if ($self->{url} =~ m#^https?://#) {
+               $path =~ s/([^a-zA-Z0-9_.-])/uc sprintf("%%%02x",ord($1))/eg;
+       }
        $self->{url} . '/' . $self->repo_path($path);
 }
 
index 27182baa84f0a39427cfb85f8ba7dd40256dbd3d..fe7b3d821567fd7a8108ae3fd7a90cdd5baac72a 100644 (file)
@@ -12,7 +12,7 @@ URL:          http://kernel.org/pub/software/scm/git/
 Source:        http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz
 BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel  %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
 BuildRoot:     %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-Requires:      git-core, git-svn, git-cvs, git-arch, git-email, gitk, git-gui, git-p4, perl-Git
+Requires:      git-core, git-svn, git-cvs, git-arch, git-email, gitk, git-gui, perl-Git
 
 %description
 Git is a fast, scalable, distributed revision control system with an
@@ -53,13 +53,6 @@ Requires:       git-core = %{version}-%{release}, tla
 %description arch
 Git tools for importing Arch repositories.
 
-%package p4
-Summary:        Git tools for importing Perforce repositories
-Group:          Development/Tools
-Requires:       git-core = %{version}-%{release}, python
-%description p4
-Git tools for importing Perforce repositories.
-
 %package email
 Summary:        Git tools for sending email
 Group:          Development/Tools
@@ -95,14 +88,14 @@ Perl interface to Git
 %setup -q
 
 %build
-make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_P4IMPORT=YesPlease \
+make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" \
      ETC_GITCONFIG=/etc/gitconfig \
-     prefix=%{_prefix} PYTHON_PATH=%{python_path} all %{!?_without_docs: doc}
+     prefix=%{_prefix} all %{!?_without_docs: doc}
 
 %install
 rm -rf $RPM_BUILD_ROOT
 make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" DESTDIR=$RPM_BUILD_ROOT \
-     WITH_P4IMPORT=YesPlease prefix=%{_prefix} mandir=%{_mandir} \
+     prefix=%{_prefix} mandir=%{_mandir} \
      ETC_GITCONFIG=/etc/gitconfig \
      PYTHON_PATH=%{python_path} \
      INSTALLDIRS=vendor install %{!?_without_docs: install-doc}
@@ -110,10 +103,10 @@ find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
 
-(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "p4import|archimport|svn|cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@)               > bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "archimport|svn|cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@)               > bin-man-doc-files
 (find $RPM_BUILD_ROOT%{perl_vendorlib} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files
 %if %{!?_without_docs:1}0
-(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "p4import|archimport|svn|git-cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "archimport|svn|git-cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
 %else
 rm -rf $RPM_BUILD_ROOT%{_mandir}
 %endif
@@ -145,13 +138,6 @@ rm -rf $RPM_BUILD_ROOT
 %{!?_without_docs: %{_mandir}/man1/git-archimport.1*}
 %{!?_without_docs: %doc Documentation/git-archimport.html }
 
-%files p4
-%defattr(-,root,root)
-%doc Documentation/git-p4import.txt
-%{_bindir}/git-p4import
-%{!?_without_docs: %{_mandir}/man1/git-p4import.1*}
-%{!?_without_docs: %doc Documentation/git-p4import.html }
-
 %files email
 %defattr(-,root,root)
 %doc Documentation/*email*.txt
@@ -187,6 +173,9 @@ rm -rf $RPM_BUILD_ROOT
 %{!?_without_docs: %doc Documentation/technical}
 
 %changelog
+* Sun Jul 15 2007 Sean Estabrooks <seanlkml@sympatico.ca>
+- Removed p4import.
+
 * Tue Jun 26 2007 Quy Tonthat <qtonthat@gmail.com>
 - Fixed problems looking for wrong manpages.
 
index f6fe78cd278bd25f47b8c17e14f5f419d639fb7a..8d4a44721380213369668ea74dc4dbe3cf360485 100644 (file)
@@ -48,3 +48,37 @@ svnrepo="file://$svnrepo"
 poke() {
        test-chmtime +1 "$1"
 }
+
+SVN_HTTPD_MODULE_PATH=${SVN_HTTPD_MODULE_PATH-'/usr/lib/apache2/modules'}
+SVN_HTTPD_PATH=${SVN_HTTPD_PATH-'/usr/sbin/apache2'}
+
+start_httpd () {
+       if test -z "$SVN_HTTPD_PORT"
+       then
+               echo >&2 'SVN_HTTPD_PORT is not defined!'
+               return
+       fi
+
+       mkdir "$GIT_DIR"/logs
+
+       cat > "$GIT_DIR/httpd.conf" <<EOF
+ServerName "git-svn test"
+ServerRoot "$GIT_DIR"
+DocumentRoot "$GIT_DIR"
+PidFile "$GIT_DIR/httpd.pid"
+Listen 127.0.0.1:$SVN_HTTPD_PORT
+LoadModule dav_module $SVN_HTTPD_MODULE_PATH/mod_dav.so
+LoadModule dav_svn_module $SVN_HTTPD_MODULE_PATH/mod_dav_svn.so
+<Location /svn>
+       DAV svn
+       SVNPath $rawsvnrepo
+</Location>
+EOF
+       "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k start
+       svnrepo=http://127.0.0.1:$SVN_HTTPD_PORT/svn
+}
+
+stop_httpd () {
+       test -z "$SVN_HTTPD_PORT" && return
+       "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k stop
+}
index 5e91db64e95d25049e298f37a2dadb436af8c8d0..e8ce7cdb833e3c0f45ffb52d85d7572839e3e34a 100755 (executable)
@@ -21,6 +21,10 @@ subcommands of git-submodule.
 #  -add an entry to .gitmodules for submodule 'example'
 #
 test_expect_success 'Prepare submodule testing' '
+       : > t &&
+       git-add t &&
+       git-commit -m "initial commit" &&
+       git branch initial HEAD &&
        mkdir lib &&
        cd lib &&
        git init &&
@@ -166,4 +170,9 @@ test_expect_success 'status should be "up-to-date" after update' '
        git-submodule status | grep "^ $rev1"
 '
 
+test_expect_success 'checkout superproject with subproject already present' '
+       git-checkout initial &&
+       git-checkout master
+'
+
 test_done
diff --git a/t/t9115-git-svn-dcommit-funky-renames.sh b/t/t9115-git-svn-dcommit-funky-renames.sh
new file mode 100755 (executable)
index 0000000..182299c
--- /dev/null
@@ -0,0 +1,54 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+
+
+test_description='git-svn dcommit can commit renames of files with ugly names'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'load repository with strange names' "
+       svnadmin load -q $rawsvnrepo < ../t9115/funky-names.dump &&
+       start_httpd
+       "
+
+test_expect_success 'init and fetch repository' "
+       git svn init $svnrepo &&
+       git svn fetch &&
+       git reset --hard git-svn
+       "
+
+test_expect_success 'create file in existing ugly and empty dir' '
+       mkdir "#{bad_directory_name}" &&
+       echo hi > "#{bad_directory_name}/ foo" &&
+       git update-index --add "#{bad_directory_name}/ foo" &&
+       git commit -m "new file in ugly parent" &&
+       git svn dcommit
+       '
+
+test_expect_success 'rename ugly file' '
+       git mv "#{bad_directory_name}/ foo" "file name with feces" &&
+       git commit -m "rename ugly file" &&
+       git svn dcommit
+       '
+
+test_expect_success 'rename pretty file' '
+       echo :x > pretty &&
+       git update-index --add pretty &&
+       git commit -m "pretty :x" &&
+       git svn dcommit &&
+       mkdir regular_dir_name &&
+       git mv pretty regular_dir_name/pretty &&
+       git commit -m "moved pretty file" &&
+       git svn dcommit
+       '
+
+test_expect_success 'rename pretty file into ugly one' '
+       git mv regular_dir_name/pretty "#{bad_directory_name}/ booboo" &&
+       git commit -m booboo &&
+       git svn dcommit
+       '
+
+stop_httpd
+
+test_done
diff --git a/t/t9115/funky-names.dump b/t/t9115/funky-names.dump
new file mode 100644 (file)
index 0000000..42422f7
--- /dev/null
@@ -0,0 +1,103 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 819c44fe-2bcc-4066-88e4-985e2bc0b418
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2007-07-12T07:54:26.062914Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 152
+Content-length: 152
+
+K 7
+svn:log
+V 44
+what will those wacky people think of next?
+
+K 10
+svn:author
+V 12
+normalperson
+K 8
+svn:date
+V 27
+2007-07-12T08:00:05.011573Z
+PROPS-END
+
+Node-path:  leading space
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path:  leading space file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: e4fa20c67542cdc21271e08d329397ab
+Content-length: 15
+
+PROPS-END
+ugly
+
+
+Node-path: #{bad_directory_name}
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: #{cool_name}
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 18
+Text-content-md5: 87dac40ca337dfa3dcc8911388c3ddda
+Content-length: 28
+
+PROPS-END
+strange name here
+
+
+Node-path: dir name with spaces
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: file name with spaces
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: c1f10cfd640618484a2a475c11410fd3
+Content-length: 17
+
+PROPS-END
+spaces
+
+
+Node-path: regular_dir_name
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
index 89dd279f89c7648ec333c59634b3169251eec7ab..7cc029e564f3a13b3352bb5f5feae12254421f68 100644 (file)
@@ -5,6 +5,7 @@
 #include "cache-tree.h"
 #include "unpack-trees.h"
 #include "progress.h"
+#include "refs.h"
 
 #define DBRT_DEBUG 1
 
@@ -425,11 +426,24 @@ static void invalidate_ce_path(struct cache_entry *ce)
                cache_tree_invalidate_path(active_cache_tree, ce->name);
 }
 
-static int verify_clean_subdirectory(const char *path, const char *action,
+/*
+ * Check that checking out ce->sha1 in subdir ce->name is not
+ * going to overwrite any working files.
+ *
+ * Currently, git does not checkout subprojects during a superproject
+ * checkout, so it is not going to overwrite anything.
+ */
+static int verify_clean_submodule(struct cache_entry *ce, const char *action,
+                                     struct unpack_trees_options *o)
+{
+       return 0;
+}
+
+static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
                                      struct unpack_trees_options *o)
 {
        /*
-        * we are about to extract "path"; we would not want to lose
+        * we are about to extract "ce->name"; we would not want to lose
         * anything in the existing directory there.
         */
        int namelen;
@@ -437,13 +451,24 @@ static int verify_clean_subdirectory(const char *path, const char *action,
        struct dir_struct d;
        char *pathbuf;
        int cnt = 0;
+       unsigned char sha1[20];
+
+       if (S_ISGITLINK(ntohl(ce->ce_mode)) &&
+           resolve_gitlink_ref(ce->name, "HEAD", sha1) == 0) {
+               /* If we are not going to update the submodule, then
+                * we don't care.
+                */
+               if (!hashcmp(sha1, ce->sha1))
+                       return 0;
+               return verify_clean_submodule(ce, action, o);
+       }
 
        /*
         * First let's make sure we do not have a local modification
         * in that directory.
         */
-       namelen = strlen(path);
-       pos = cache_name_pos(path, namelen);
+       namelen = strlen(ce->name);
+       pos = cache_name_pos(ce->name, namelen);
        if (0 <= pos)
                return cnt; /* we have it as nondirectory */
        pos = -pos - 1;
@@ -451,7 +476,7 @@ static int verify_clean_subdirectory(const char *path, const char *action,
                struct cache_entry *ce = active_cache[i];
                int len = ce_namelen(ce);
                if (len < namelen ||
-                   strncmp(path, ce->name, namelen) ||
+                   strncmp(ce->name, ce->name, namelen) ||
                    ce->name[namelen] != '/')
                        break;
                /*
@@ -469,16 +494,16 @@ static int verify_clean_subdirectory(const char *path, const char *action,
         * present file that is not ignored.
         */
        pathbuf = xmalloc(namelen + 2);
-       memcpy(pathbuf, path, namelen);
+       memcpy(pathbuf, ce->name, namelen);
        strcpy(pathbuf+namelen, "/");
 
        memset(&d, 0, sizeof(d));
        if (o->dir)
                d.exclude_per_dir = o->dir->exclude_per_dir;
-       i = read_directory(&d, path, pathbuf, namelen+1, NULL);
+       i = read_directory(&d, ce->name, pathbuf, namelen+1, NULL);
        if (i)
                die("Updating '%s' would lose untracked files in it",
-                   path);
+                   ce->name);
        free(pathbuf);
        return cnt;
 }
@@ -487,7 +512,7 @@ static int verify_clean_subdirectory(const char *path, const char *action,
  * We do not want to remove or overwrite a working tree file that
  * is not tracked, unless it is ignored.
  */
-static void verify_absent(const char *path, const char *action,
+static void verify_absent(struct cache_entry *ce, const char *action,
                struct unpack_trees_options *o)
 {
        struct stat st;
@@ -495,15 +520,15 @@ static void verify_absent(const char *path, const char *action,
        if (o->index_only || o->reset || !o->update)
                return;
 
-       if (has_symlink_leading_path(path, NULL))
+       if (has_symlink_leading_path(ce->name, NULL))
                return;
 
-       if (!lstat(path, &st)) {
+       if (!lstat(ce->name, &st)) {
                int cnt;
 
-               if (o->dir && excluded(o->dir, path))
+               if (o->dir && excluded(o->dir, ce->name))
                        /*
-                        * path is explicitly excluded, so it is Ok to
+                        * ce->name is explicitly excluded, so it is Ok to
                         * overwrite it.
                         */
                        return;
@@ -515,7 +540,7 @@ static void verify_absent(const char *path, const char *action,
                         * files that are in "foo/" we would lose
                         * it.
                         */
-                       cnt = verify_clean_subdirectory(path, action, o);
+                       cnt = verify_clean_subdirectory(ce, action, o);
 
                        /*
                         * If this removed entries from the index,
@@ -543,7 +568,7 @@ static void verify_absent(const char *path, const char *action,
                 * delete this path, which is in a subdirectory that
                 * is being replaced with a blob.
                 */
-               cnt = cache_name_pos(path, strlen(path));
+               cnt = cache_name_pos(ce->name, strlen(ce->name));
                if (0 <= cnt) {
                        struct cache_entry *ce = active_cache[cnt];
                        if (!ce_stage(ce) && !ce->ce_mode)
@@ -551,7 +576,7 @@ static void verify_absent(const char *path, const char *action,
                }
 
                die("Untracked working tree file '%s' "
-                   "would be %s by merge.", path, action);
+                   "would be %s by merge.", ce->name, action);
        }
 }
 
@@ -575,7 +600,7 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
                }
        }
        else {
-               verify_absent(merge->name, "overwritten", o);
+               verify_absent(merge, "overwritten", o);
                invalidate_ce_path(merge);
        }
 
@@ -590,7 +615,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
        if (old)
                verify_uptodate(old, o);
        else
-               verify_absent(ce->name, "removed", o);
+               verify_absent(ce, "removed", o);
        ce->ce_mode = 0;
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
        invalidate_ce_path(ce);
@@ -707,18 +732,18 @@ int threeway_merge(struct cache_entry **stages,
        if (o->aggressive) {
                int head_deleted = !head && !df_conflict_head;
                int remote_deleted = !remote && !df_conflict_remote;
-               const char *path = NULL;
+               struct cache_entry *ce = NULL;
 
                if (index)
-                       path = index->name;
+                       ce = index;
                else if (head)
-                       path = head->name;
+                       ce = head;
                else if (remote)
-                       path = remote->name;
+                       ce = remote;
                else {
                        for (i = 1; i < o->head_idx; i++) {
                                if (stages[i] && stages[i] != o->df_conflict_entry) {
-                                       path = stages[i]->name;
+                                       ce = stages[i];
                                        break;
                                }
                        }
@@ -733,8 +758,8 @@ int threeway_merge(struct cache_entry **stages,
                    (remote_deleted && head && head_match)) {
                        if (index)
                                return deleted_entry(index, index, o);
-                       else if (path && !head_deleted)
-                               verify_absent(path, "removed", o);
+                       else if (ce && !head_deleted)
+                               verify_absent(ce, "removed", o);
                        return 0;
                }
                /*