{"thread":{"id":"4280","subject":"[PATCH] A Perforce importer for git.","startedAt":"2006-05-24T22:04:38Z","lastAt":"2006-06-05T21:00:18Z","messageCount":6,"participants":["Sean","Alex Riesen"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"20649","messageId":"BAYC1-PASMTP117796558F3B42B2C18DBCAE980@CEZ.ICE","threadId":"4280","inReplyTo":null,"subject":"[PATCH] A Perforce importer for git.","fromName":"Sean","fromEmail":"seanlkml@sympatico.ca","sentAt":"2006-05-24T22:04:38Z","receivedAt":"2006-05-24T22:04:38Z","isPatch":true,"sender":{"key":"seanlkml@sympatico.ca","avatar":"https://gravatar.com/avatar/f92923f54fc08c401fc59b71829d4b89e9b8087fbba45ff87c82e6a83aee02ae?d=mp&s=160"},"body":"\nSigned-off-by: Sean Estabrooks <seanlkml@sympatico.ca>\n---\n Documentation/git-p4import.txt |  165 ++++++++++++++++++\n git-p4import.py                |  357 ++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 522 insertions(+), 0 deletions(-)\n\n\ndiff --git a/Documentation/git-p4import.txt b/Documentation/git-p4import.txt\nnew file mode 100644\nindex 0000000..b8ff1e9\n--- /dev/null\n+++ b/Documentation/git-p4import.txt\n@@ -0,0 +1,165 @@\n+git-p4import(1)\n+===============\n+\n+NAME\n+----\n+git-p4import - Import a Perforce repository into git\n+\n+\n+SYNOPSIS\n+--------\n+`git-p4import` [-q|-v] [--authors <file>] [-t <timezone>] <//p4repo/path> <branch>\n+\n+`git-p4import` --stitch <//p4repo/path>\n+\n+`git-p4import`\n+\n+\n+DESCRIPTION\n+-----------\n+Import a Perforce repository into an existing git repository.  When\n+a <//p4repo/path> and <branch> are specified a new branch with the\n+given name will be created and the initial import will begin.\n+\n+Once the initial import is complete you can do an incremental import\n+of new commits from the Perforce repository.  You do this by checking\n+out the appropriate git branch and then running `git-p4import` without\n+any options.\n+\n+The standard p4 client is used to communicate with the Perforce\n+repository; it must be configured correctly in order for `git-p4import`\n+to operate (see below).\n+\n+\n+OPTIONS\n+-------\n+-q::\n+\tDo not display any progress information.\n+\n+-v::\n+        Give extra progress information.\n+\n+\\--authors::\n+\tSpecify an authors file containing a mapping of Perforce user\n+\tids to full names and email addresses (see Notes below).\n+\n+\\--stitch::\n+\tImport the contents of the given perforce branch into the\n+\tcurrently checked out git branch.\n+\n+\\--log::\n+\tStore debugging information in the specified file.\n+\n+-t::\n+\tSpecify that the remote repository is in the specified timezone.\n+\tTimezone must be in the format \"US/Pacific\" or \"Europe/London\"\n+\tetc.  You only need to specify this once, it will be saved in\n+\tthe git config file for the repository.\n+\n+<//p4repo/path>::\n+\tThe Perforce path that will be imported into the specified branch.\n+\n+<branch>::\n+\tThe new branch that will be created to hold the Perforce imports.\n+\n+\n+P4 Client\n+---------\n+You must make the `p4` client command available in your $PATH and\n+configure it to communicate with the target Perforce repository.\n+Typically this means you must set the \"$P4PORT\" and \"$P4CLIENT\"\n+environment variables.\n+\n+You must also configure a `p4` client \"view\" which maps the Perforce\n+branch into the top level of your git repository, for example:\n+\n+------------\n+Client: myhost\n+\n+Root:   /home/sean/import\n+\n+Options:   noallwrite clobber nocompress unlocked modtime rmdir\n+\n+View:\n+        //public/jam/... //myhost/jam/...\n+------------\n+\n+With the above `p4` client setup, you could import the \"jam\"\n+perforce branch into a branch named \"jammy\", like so:\n+\n+------------\n+$ mkdir -p /home/sean/import/jam\n+$ cd /home/sean/import/jam\n+$ git init-db\n+$ git p4import //public/jam jammy\n+------------\n+\n+\n+Multiple Branches\n+-----------------\n+Note that by creating multiple \"views\" you can use `git-p4import`\n+to import additional branches into the same git repository.\n+However, the `p4` client has a limitation in that it silently\n+ignores all but the last \"view\" that maps into the same local\n+directory.  So the following will *not* work:\n+\n+------------\n+View:\n+        //public/jam/... //myhost/jam/...\n+        //public/other/... //myhost/jam/...\n+        //public/guest/... //myhost/jam/...\n+------------\n+\n+If you want more than one Perforce branch to be imported into the\n+same directory you must employ a workaround.  A simple option is\n+to adjust your `p4` client before each import to only include a\n+single view.\n+\n+Another option is to create multiple symlinks locally which all\n+point to the same directory in your git repository and then use\n+one per \"view\" instead of listing the actual directory.\n+\n+\n+Tags\n+----\n+A git tag of the form p4/xx is created for every change imported from\n+the Perforce repository where xx is the Perforce changeset number.\n+Therefore after the import you can use git to access any commit by its\n+Perforce number, eg. git show p4/327.\n+\n+The tag associated with the HEAD commit is also how `git-p4import`\n+determines if their are new changes to incrementally import from the\n+Perforce repository.\n+\n+If you import from a repository with many thousands of changes\n+you will have an equal number of p4/xxxx git tags.  Git tags can\n+be expensive in terms of disk space and repository operations.\n+If you don't need to perform further incremental imports, you\n+may delete the tags.\n+\n+\n+Notes\n+-----\n+You can interrupt the import (eg. ctrl-c) at any time and restart it\n+without worry.\n+\n+Author information is automatically determined by querying the\n+Perforce \"users\" table using the id associated with each change.\n+However, if you want to manually supply these mappings you can do\n+so with the \"--authors\" option.  It accepts a file containing a list\n+of mappings with each line containing one mapping in the format:\n+\n+------------\n+    perforce_id = Full Name <email@address.com>\n+------------\n+\n+\n+Author\n+------\n+Written by Sean Estabrooks <seanlkml@sympatico.ca>\n+\n+\n+GIT\n+---\n+Part of the gitlink:git[7] suite\n+\ndiff --git a/git-p4import.py b/git-p4import.py\nnew file mode 100644\nindex 0000000..74172ab\n--- /dev/null\n+++ b/git-p4import.py\n@@ -0,0 +1,357 @@\n+#!/usr/bin/python\n+#\n+# This tool is copyright (c) 2006, Sean Estabrooks.\n+# It is released under the Gnu Public License, version 2.\n+#\n+# Import Perforce branches into Git repositories.\n+# Checking out the files is done by calling the standard p4\n+# client which you must have properly configured yourself\n+#\n+\n+import marshal\n+import os\n+import sys\n+import time\n+import getopt\n+\n+from signal import signal, \\\n+   SIGPIPE, SIGINT, SIG_DFL, \\\n+   default_int_handler\n+\n+signal(SIGPIPE, SIG_DFL)\n+s = signal(SIGINT, SIG_DFL)\n+if s != default_int_handler:\n+   signal(SIGINT, s)\n+\n+\n+def die(msg, *args):\n+    for a in args:\n+        msg = \"%s %s\" % (msg, a)\n+    print \"git-p4import fatal error:\", msg\n+    sys.exit(1)\n+\n+def usage():\n+    print \"USAGE: git-p4import [-q|-v]  [--authors=<file>]  [-t <timezone>]  [//p4repo/path <branch>]\"\n+    sys.exit(1)\n+\n+verbosity = 1\n+logfile = \"/dev/null\"\n+ignore_warnings = False\n+stitch = 0\n+\n+def report(level, msg, *args):\n+    global verbosity\n+    global logfile\n+    for a in args:\n+        msg = \"%s %s\" % (msg, a)\n+    fd = open(logfile, \"a\")\n+    fd.writelines(msg)\n+    fd.close()\n+    if level <= verbosity:\n+        print msg\n+\n+class p4_command:\n+    def __init__(self, _repopath):\n+        try:\n+            global logfile\n+            self.userlist = {}\n+            if _repopath[-1] == '/':\n+                self.repopath = _repopath[:-1]\n+            else:\n+                self.repopath = _repopath\n+            if self.repopath[-4:] != \"/...\":\n+                self.repopath= \"%s/...\" % self.repopath\n+            f=os.popen('p4 -V 2>>%s'%logfile, 'rb')\n+            a = f.readlines()\n+            if f.close():\n+                raise\n+        except:\n+                die(\"Could not find the \\\"p4\\\" command\")\n+\n+    def p4(self, cmd, *args):\n+        global logfile\n+        cmd = \"%s %s\" % (cmd, ' '.join(args))\n+        report(2, \"P4:\", cmd)\n+        f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb')\n+        list = []\n+        while 1:\n+           try:\n+                list.append(marshal.load(f))\n+           except EOFError:\n+                break\n+        self.ret = f.close()\n+        return list\n+\n+    def sync(self, id, force=False, trick=False, test=False):\n+        if force:\n+            ret = self.p4(\"sync -f %s@%s\"%(self.repopath, id))[0]\n+        elif trick:\n+            ret = self.p4(\"sync -k %s@%s\"%(self.repopath, id))[0]\n+        elif test:\n+            ret = self.p4(\"sync -n %s@%s\"%(self.repopath, id))[0]\n+        else:\n+            ret = self.p4(\"sync    %s@%s\"%(self.repopath, id))[0]\n+        if ret['code'] == \"error\":\n+             data = ret['data'].upper()\n+             if data.find('VIEW') > 0:\n+                 die(\"Perforce reports %s is not in client view\"% self.repopath)\n+             elif data.find('UP-TO-DATE') < 0:\n+                 die(\"Could not sync files from perforce\", self.repopath)\n+\n+    def changes(self, since=0):\n+        try:\n+            list = []\n+            for rec in self.p4(\"changes %s@%s,#head\" % (self.repopath, since+1)):\n+                list.append(rec['change'])\n+            list.reverse()\n+            return list\n+        except:\n+            return []\n+\n+    def authors(self, filename):\n+        f=open(filename)\n+        for l in f.readlines():\n+            self.userlist[l[:l.find('=')].rstrip()] = \\\n+                    (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')])\n+        f.close()\n+        for f,e in self.userlist.items():\n+                report(2, f, \":\", e[0], \"  <\", e[1], \">\")\n+\n+    def _get_user(self, id):\n+        if not self.userlist.has_key(id):\n+            try:\n+                user = self.p4(\"users\", id)[0]\n+                self.userlist[id] = (user['FullName'], user['Email'])\n+            except:\n+                self.userlist[id] = (id, \"\")\n+        return self.userlist[id]\n+\n+    def _format_date(self, ticks):\n+        symbol='+'\n+        name = time.tzname[0]\n+        offset = time.timezone\n+        if ticks[8]:\n+            name = time.tzname[1]\n+            offset = time.altzone\n+        if offset < 0:\n+            offset *= -1\n+            symbol = '-'\n+        localo = \"%s%02d%02d %s\" % (symbol, offset / 3600, offset % 3600, name)\n+        tickso = time.strftime(\"%a %b %d %H:%M:%S %Y\", ticks)\n+        return \"%s %s\" % (tickso, localo)\n+\n+    def where(self):\n+        try:\n+            return self.p4(\"where %s\" % self.repopath)[-1]['path']\n+        except:\n+            return \"\"\n+\n+    def describe(self, num):\n+        desc = self.p4(\"describe -s\", num)[0]\n+        self.msg = desc['desc']\n+        self.author, self.email = self._get_user(desc['user'])\n+        self.date = self._format_date(time.localtime(long(desc['time'])))\n+        return self\n+\n+class git_command:\n+    def __init__(self):\n+        try:\n+            self.version = self.git(\"--version\")[0][12:].rstrip()\n+        except:\n+            die(\"Could not find the \\\"git\\\" command\")\n+        try:\n+            self.gitdir = self.get_single(\"rev-parse --git-dir\")\n+            report(2, \"gdir:\", self.gitdir)\n+        except:\n+            die(\"Not a git repository... did you forget to \\\"git init-db\\\" ?\")\n+        try:\n+            self.cdup = self.get_single(\"rev-parse --show-cdup\")\n+            if self.cdup != \"\":\n+                os.chdir(self.cdup)\n+            self.topdir = os.getcwd()\n+            report(2, \"topdir:\", self.topdir)\n+        except:\n+            die(\"Could not find top git directory\")\n+\n+    def git(self, cmd):\n+        global logfile\n+        report(2, \"GIT:\", cmd)\n+        f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb')\n+        r=f.readlines()\n+        self.ret = f.close()\n+        return r\n+\n+    def get_single(self, cmd):\n+        return self.git(cmd)[0].rstrip()\n+\n+    def current_branch(self):\n+        try:\n+            testit = self.git(\"rev-parse --verify HEAD\")[0]\n+            return self.git(\"symbolic-ref HEAD\")[0][11:].rstrip()\n+        except:\n+            return None\n+\n+    def get_config(self, variable):\n+        try:\n+            return self.git(\"repo-config --get %s\" % variable)[0].rstrip()\n+        except:\n+            return None\n+\n+    def set_config(self, variable, value):\n+        try:\n+            self.git(\"repo-config %s %s\"%(variable, value) )\n+        except:\n+            die(\"Could not set %s to \" % variable, value)\n+\n+    def make_tag(self, name, head):\n+        self.git(\"tag -f %s %s\"%(name,head))\n+\n+    def top_change(self, branch):\n+        try:\n+            a=self.get_single(\"name-rev --tags refs/heads/%s\" % branch)\n+            loc = a.find(' tags/') + 6\n+            if a[loc:loc+3] != \"p4/\":\n+                raise\n+            return int(a[loc+3:][:-2])\n+        except:\n+            return 0\n+\n+    def update_index(self):\n+        self.git(\"ls-files -m -d -o -z | git update-index --add --remove -z --stdin\")\n+\n+    def checkout(self, branch):\n+        self.git(\"checkout %s\" % branch)\n+\n+    def repoint_head(self, branch):\n+        self.git(\"symbolic-ref HEAD refs/heads/%s\" % branch)\n+\n+    def remove_files(self):\n+        self.git(\"ls-files | xargs rm\")\n+\n+    def clean_directories(self):\n+        self.git(\"clean -d\")\n+\n+    def fresh_branch(self, branch):\n+        report(1, \"Creating new branch\", branch)\n+        self.git(\"ls-files | xargs rm\")\n+        os.remove(\".git/index\")\n+        self.repoint_head(branch)\n+        self.git(\"clean -d\")\n+\n+    def basedir(self):\n+        return self.topdir\n+\n+    def commit(self, author, email, date, msg, id):\n+        self.update_index()\n+        fd=open(\".msg\", \"w\")\n+        fd.writelines(msg)\n+        fd.close()\n+        try:\n+                current = self.get_single(\"rev-parse --verify HEAD\")\n+                head = \"-p HEAD\"\n+        except:\n+                current = \"\"\n+                head = \"\"\n+        tree = self.get_single(\"write-tree\")\n+        for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]:\n+            os.environ['GIT_AUTHOR_%s'%r] = l\n+            os.environ['GIT_COMMITTER_%s'%r] = l\n+        commit = self.get_single(\"commit-tree %s %s < .msg\" % (tree,head))\n+        os.remove(\".msg\")\n+        self.make_tag(\"p4/%s\"%id, commit)\n+        self.git(\"update-ref HEAD %s %s\" % (commit, current) )\n+\n+\n+try:\n+    opts, args = getopt.getopt(sys.argv[1:], \"qhvt:\",\n+                    [\"authors=\",\"help\",\"stitch=\",\"timezone=\",\"log=\",\"ignore\"])\n+except getopt.GetoptError:\n+    usage()\n+\n+for o, a in opts:\n+    if o == \"-q\":\n+        verbosity = 0\n+    if o == \"-v\":\n+        verbosity += 1\n+    if o in (\"--log\"):\n+        logfile = a\n+    if o in (\"-h\", \"--help\"):\n+        usage()\n+    if o in (\"--ignore\"):\n+        ignore_warnings = True\n+\n+git = git_command()\n+branch=git.current_branch()\n+\n+for o, a in opts:\n+    if o in (\"-t\", \"--timezone\"):\n+        git.set_config(\"perforce.timezone\", a)\n+    if o in (\"--stitch\"):\n+        git.set_config(\"perforce.%s.path\" % branch, a)\n+        stitch = 1\n+\n+if len(args) == 2:\n+    branch = args[1]\n+    git.checkout(branch)\n+    if branch == git.current_branch():\n+        die(\"Branch %s already exists!\" % branch)\n+    report(1, \"Setting perforce to \", args[0])\n+    git.set_config(\"perforce.%s.path\" % branch, args[0])\n+elif len(args) != 0:\n+    die(\"You must specify the perforce //depot/path and git branch\")\n+\n+p4path = git.get_config(\"perforce.%s.path\" % branch)\n+if p4path == None:\n+    die(\"Do not know Perforce //depot/path for git branch\", branch)\n+\n+p4 = p4_command(p4path)\n+\n+for o, a in opts:\n+    if o in (\"-a\", \"--authors\"):\n+        p4.authors(a)\n+\n+localdir = git.basedir()\n+if p4.where()[:len(localdir)] != localdir:\n+    report(1, \"**WARNING** Appears p4 client is misconfigured\")\n+    report(1, \"   for sync from %s to %s\" % (p4.repopath, localdir))\n+    if ignore_warnings != True:\n+        die(\"Reconfigure or use \\\"--ignore\\\" on command line\")\n+\n+if stitch == 0:\n+    top = git.top_change(branch)\n+else:\n+    top = 0\n+changes = p4.changes(top)\n+count = len(changes)\n+if count == 0:\n+    report(1, \"Already up to date...\")\n+    sys.exit(0)\n+\n+ptz = git.get_config(\"perforce.timezone\")\n+if ptz:\n+    report(1, \"Setting timezone to\", ptz)\n+    os.environ['TZ'] = ptz\n+    time.tzset()\n+\n+if stitch == 1:\n+    git.remove_files()\n+    git.clean_directories()\n+    p4.sync(changes[0], force=True)\n+elif top == 0 and branch != git.current_branch():\n+    p4.sync(changes[0], test=True)\n+    report(1, \"Creating new initial commit\");\n+    git.fresh_branch(branch)\n+    p4.sync(changes[0], force=True)\n+else:\n+    p4.sync(changes[0], trick=True)\n+\n+report(1, \"processing %s changes from p4 (%s) to git (%s)\" % (count, p4.repopath, branch))\n+for id in changes:\n+    report(1, \"Importing changeset\", id)\n+    change = p4.describe(id)\n+    p4.sync(id)\n+    git.commit(change.author, change.email, change.date, change.msg, id)\n+    if stitch == 1:\n+        git.clean_directories()\n+        stitch = 0\n+\n-- \n1.3.3.g17cf3\n"},{"id":"21097","messageId":"81b0412b0606020609o24ee34b4p7b1bcd013136d49a@mail.gmail.com","threadId":"4280","inReplyTo":"BAYC1-PASMTP117796558F3B42B2C18DBCAE980@CEZ.ICE","subject":"Re: [PATCH] A Perforce importer for git.","fromName":"Alex Riesen","fromEmail":"raa.lkml@gmail.com","sentAt":"2006-06-02T13:09:45Z","receivedAt":"2006-06-02T13:09:45Z","isPatch":true,"sender":{"key":"raa.lkml@gmail.com","avatar":"https://avatars.githubusercontent.com/u/324101?v=4"},"body":"On 5/25/06, Sean <seanlkml@sympatico.ca> wrote:\n>\n> Signed-off-by: Sean Estabrooks <seanlkml@sympatico.ca>\n> ---\n>  Documentation/git-p4import.txt |  165 ++++++++++++++++++\n>  git-p4import.py                |  357 ++++++++++++++++++++++++++++++++++++++++\n>  2 files changed, 522 insertions(+), 0 deletions(-)\n\nBTW, can I suggest to import the _currently_ synced state?\n\nThe reason is that because of the way how Perforce is done\nits working directories (views, aka clients) are often a\nhorrible mix of occasionally synced files to some random\nversions. No one actually uses \"p4 sync\" for whole project here\nwhere I work, because it is absolutely useless at this level\n(updated files have abolutely no relevance at head revision,\nwhich is what you get by syncing without strictly specifying\na revision). So a working state is stitched together from\na lot of \"mappings\": perforce path to local path -> revision.\nThat state can be actually worked on (up until you have\nto commit something, that is not possible except on head).\n\nThat is why I actually can't sync before importing, but have\nto use the method Junio described for CVS:\np4 sync files...#revisions\ngit add new-files...\ngit commit -a\n(simplified. There are problems with case-sensitivity and that\nstupid read-only attribute perfoce uses as sync marker).\n"},{"id":"21100","messageId":"BAYC1-PASMTP04855AA99A546A74A7F07BAE910@CEZ.ICE","threadId":"4280","inReplyTo":"81b0412b0606020609o24ee34b4p7b1bcd013136d49a@mail.gmail.com","subject":"Re: [PATCH] A Perforce importer for git.","fromName":"Sean","fromEmail":"seanlkml@sympatico.ca","sentAt":"2006-06-02T13:43:57Z","receivedAt":"2006-06-02T13:43:57Z","isPatch":true,"sender":{"key":"seanlkml@sympatico.ca","avatar":"https://gravatar.com/avatar/f92923f54fc08c401fc59b71829d4b89e9b8087fbba45ff87c82e6a83aee02ae?d=mp&s=160"},"body":"On Fri, 2 Jun 2006 15:09:45 +0200\n\"Alex Riesen\" <raa.lkml@gmail.com> wrote:\n\n> BTW, can I suggest to import the _currently_ synced state?\n> \n> The reason is that because of the way how Perforce is done\n> its working directories (views, aka clients) are often a\n> horrible mix of occasionally synced files to some random\n> versions. No one actually uses \"p4 sync\" for whole project here\n> where I work, because it is absolutely useless at this level\n> (updated files have abolutely no relevance at head revision,\n> which is what you get by syncing without strictly specifying\n> a revision). So a working state is stitched together from\n> a lot of \"mappings\": perforce path to local path -> revision.\n> That state can be actually worked on (up until you have\n> to commit something, that is not possible except on head). \n\nHey Alex,\n\nI'd happily change the script to accommodate your needs if it's at all\nreasonable.  As you've no doubt gathered, the script is currently very\nbranch centric.  It needs a mapping view from each p4 branch into the\nlocal git directory.  Then it does a sync //p4/mapping@{revision} for\neach revision along each mapped branch and commits it into git.\n\nNote that it never syncs specifically to head, it explicitly asks for\neach revision along the branch and tt doesn't know anything about your\nworking state, it can only import commits.\n\nAre you looking for the ability to create a single git branch which has\nthe history of the combined view of your stitched together client mappings\nrather than the independent branches held by the server?\n\nSean\n"},{"id":"21117","messageId":"20060602212005.GA7801@steel.home","threadId":"4280","inReplyTo":"20060602094357.ee3d8407.seanlkml@sympatico.ca","subject":"Re: [PATCH] A Perforce importer for git.","fromName":"Alex Riesen","fromEmail":"fork0@t-online.de","sentAt":"2006-06-02T21:20:05Z","receivedAt":"2006-06-02T21:20:05Z","isPatch":true,"sender":{"key":"raa.lkml@gmail.com","avatar":"https://avatars.githubusercontent.com/u/324101?v=4"},"body":"Sean, Fri, Jun 02, 2006 15:43:57 +0200:\n> \"Alex Riesen\" <raa.lkml@gmail.com> wrote:\n> \n> > BTW, can I suggest to import the _currently_ synced state?\n> > \n> \n> Note that it never syncs specifically to head, it explicitly asks for\n> each revision along the branch and tt doesn't know anything about your\n> working state, it can only import commits.\n\nSo did get that part right (I didn't actually tried your script, just\nread the code).\n\n> Are you looking for the ability to create a single git branch which has\n\nWell, I'm not quite sure it's at all possible... Perforce has a\nstrange ways for doing history: it is kept for each file (as in CVS),\nbut you can bundle changes in many files into one \"change\".\nPerforce also has no branches (in the GIT's meaning of the term).\nThese by Perforce are just server-side directories, without any\nrelevance to the source whatsoever (just copies).\n\n> the history of the combined view of your stitched together client mappings\n> rather than the independent branches held by the server?\n\nI'm rather looking for a ability to manage a single branch where\nimport \"sync\" events appear as a merge of changes to the files\ninvolved in the sync. I just haven't figured out yet how to \"break\" a\nPerforce change into changes to single files and import that broken up\ncommit into git as a merge.\n\nI hope that was clear enough... I think understanding how cvs/svn\nimports work should help here - Perforce is actually nothing very\ndifferent from them.\n"},{"id":"21207","messageId":"BAYC1-PASMTP01323F67D0088CA96D86BEAE970@CEZ.ICE","threadId":"4280","inReplyTo":"20060602212005.GA7801@steel.home","subject":"Re: [PATCH] A Perforce importer for git.","fromName":"Sean","fromEmail":"seanlkml@sympatico.ca","sentAt":"2006-06-04T14:04:30Z","receivedAt":"2006-06-04T14:04:30Z","isPatch":true,"sender":{"key":"seanlkml@sympatico.ca","avatar":"https://gravatar.com/avatar/f92923f54fc08c401fc59b71829d4b89e9b8087fbba45ff87c82e6a83aee02ae?d=mp&s=160"},"body":"On Fri, 2 Jun 2006 23:20:05 +0200\nfork0@t-online.de (Alex Riesen) wrote:\n\n> Well, I'm not quite sure it's at all possible... Perforce has a\n> strange ways for doing history: it is kept for each file (as in CVS),\n> but you can bundle changes in many files into one \"change\".\n> Perforce also has no branches (in the GIT's meaning of the term).\n> These by Perforce are just server-side directories, without any\n> relevance to the source whatsoever (just copies).\n\nYes, each Perforce change records a new file revision level\nfor each of the files involved in the changeset.\n \n> I'm rather looking for a ability to manage a single branch where\n> import \"sync\" events appear as a merge of changes to the files\n> involved in the sync. I just haven't figured out yet how to \"break\" a\n> Perforce change into changes to single files and import that broken up\n> commit into git as a merge.\n\nAhh, so what you're really asking is for a way to maintain the perforce\nmerge history within git.  Whereas the current p4import script just\nshows a linear history without any merges.\n\nThe problem is that Perforce doesn't merge at the commit level.  It\nallows changes from other branches to be pulled one file at a time and\nfrom any rev level.\n\nNow, even if you break those changes into one git commit per file per\nrevision level (yuck!), you still couldn't use them to record Perforce\nmerges.  Git would still merge the entire history of such commits from\nthe other branch whenever you tried to merge just one.\n\nAFAICS, the best you could do would be to create cherry-picks, plucking\njust the commits from the other branch you want.  However at that point\nyou're not getting a git merge anyway and it doesn't seem to be any\nbenefit beyond what the importer already does.  Well, the importer\n_could_ make a comment in the commit message describing where each\nfile change originated (ie. from which branch/rev).  Would that help?\n\nSean\n"},{"id":"21281","messageId":"20060605210018.GA22650@steel.home","threadId":"4280","inReplyTo":"20060604100430.cb2789dd.seanlkml@sympatico.ca","subject":"Re: [PATCH] A Perforce importer for git.","fromName":"Alex Riesen","fromEmail":"fork0@t-online.de","sentAt":"2006-06-05T21:00:18Z","receivedAt":"2006-06-05T21:00:18Z","isPatch":true,"sender":{"key":"raa.lkml@gmail.com","avatar":"https://avatars.githubusercontent.com/u/324101?v=4"},"body":"Sean, Sun, Jun 04, 2006 16:04:30 +0200:\n> > I'm rather looking for a ability to manage a single branch where\n> > import \"sync\" events appear as a merge of changes to the files\n> > involved in the sync. I just haven't figured out yet how to \"break\" a\n> > Perforce change into changes to single files and import that broken up\n> > commit into git as a merge.\n> \n> Ahh, so what you're really asking is for a way to maintain the perforce\n> merge history within git.  Whereas the current p4import script just\n> shows a linear history without any merges.\n\nI assume that by \"perforce merge\" you understand the set of revisions\nin the working directory. That's what you get in a typical corporate\nenvironment with hundreds libraries and source files somehow stitched\ntogether in a hope it'd work. It does, surprisingly often. There is\nalso another \"merge\" in perforce workflow - plain text merge of many\nfiles, done manually in working directory and checked in afterwards.\nI believe it wouldn't be possible to get a history of this merge,\nbecause there is just no information about the merge anywhere.\n\n> The problem is that Perforce doesn't merge at the commit level.  It\n> allows changes from other branches to be pulled one file at a time and\n> from any rev level.\n\nRight. Awkward.\n\n> Now, even if you break those changes into one git commit per file per\n> revision level (yuck!), you still couldn't use them to record Perforce\n> merges.  Git would still merge the entire history of such commits from\n> the other branch whenever you tried to merge just one.\n\nI think it's worse: you can't merge (as in git) anything because of\nthat salad from local (working) and remote (p4 server-side) pathnames.\n\n> AFAICS, the best you could do would be to create cherry-picks, plucking\n> just the commits from the other branch you want.  However at that point\n> you're not getting a git merge anyway and it doesn't seem to be any\n> benefit beyond what the importer already does.  Well, the importer\n> _could_ make a comment in the commit message describing where each\n> file change originated (ie. from which branch/rev).  Would that help?\n\nDon't think so, even if this is surely better detailed this way. I\nstill wont have the ability to merge branches. Maybe if every change\nto every file gets it own commit one can use that information to\neither cherry-pick the changes or fix the pathnames and apply that\npatch? And a P4 change could be represented as a git-merge.\n\nLike this:\n\nP4:\n    Change 213412\n    a/foo.c #3\n    b/bar.c #6\n\nGit:\n     +--commit abcdef ----+\n     |  a/foo.c +3 lines  |\nbase-+                    commit deffff (merge, represents Change 213412)\n     |  commit abcccd     |\n     +--b/bar.c -3 lines -+\n\nNow I can cherry-pick (or just copy) commit \"abcdef\" or commit\n\"abcccd\", and still can find out what that \"Change 213412\" was all\nabout.\n"}]}