{"thread":{"id":"16653","subject":"New script to convert p4 repositories to git - git-p4c version 1.","startedAt":"2008-12-09T10:25:17Z","lastAt":"2008-12-17T14:04:19Z","messageCount":5,"participants":["John Chapman","Jakub Narebski","Pete Wyckoff"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"97429","messageId":"1228818317.5504.23.camel@localhost","threadId":"16653","inReplyTo":null,"subject":"New script to convert p4 repositories to git - git-p4c version 1.","fromName":"John Chapman","fromEmail":"thestar@fussycoder.id.au","sentAt":"2008-12-09T10:25:17Z","receivedAt":"2008-12-09T10:25:17Z","isPatch":false,"sender":{"key":"thestar@fussycoder.id.au","avatar":null},"body":"I couldn't use git-p4 on my system because I kept running out of memory,\nand I didn't like the workflow it imposed.\nAlso, it had various other issues with the repo I was trying to use,\nmainly because it is not an ideal repository, however those are\n(generally) the fault of the particular repo I was using, and not\ngit-p4. (Which is an excellent script by itself).\n\nThis script is severely crippled in that it doesn't (yet) allow one to\ncontribute changesets back to perforce, however it manages to read from\nperforce with:\n* No need to rebase.\n* Mangling of file names. (Especially with regards to case sensitivity).\n* Tagging of revisions with the perforce changesets.\n* Ability to handle branches with spaces in the name.\n* Ability to pretend that perforce doesn't exist. (That's the plan,\nanyway).\n* Be extremely memory efficient. It does NOT require as much memory as\ndoes git-p4, even when the size of the change is large.\n* Be easy to manually modify the repository, particularly if bad things\nhappen.\n\nUnfortunately, not all of the above features may be reliable yet,\nhowever I offer this script in order to obtain hopefully constructive\nfeedback so that I may improve the script and make it work very well.\n\nOnce I perfect this script, I plan to work on getting changes from git\nback into perforce, which I have a few ideas as to how I might do it.\n(None of which require rebasing).\n\nIt requires an OS that can efficiently utilise many open files and\npipes, and can run many processes. Such as Linux.  I seriously doubt it\ncan work on Windows.\n\nIt is called git-p4c, because 'git-p4' was taken, and I intended to\nwrite it in C++.  I may still rewrite it in C++ if it is found\nneccessary to use it on windows. (The Perforce C++ ABI will remove the\nneed to fork so many processes), but I won't be doing that before I\nimplement the write to perforce support.\n\nConsider this to be experimental, not yet worthy of a version number.\n\nRemember, I crave (constructive) feedback.\n\nThankyou.\n\n\n#!/usr/bin/env python\n\nUSAGE = r'''\ngit-p4c - written by John Chapman.\n\nLicense:\n    You are free to use this under the terms of the GPL License\n    http://www.fsf.org/licensing/licenses/gpl.html\n\n    I may change the license at any date in the future, unless\n    I have substantial contributions, but regardless of what licence\n    I choose, it will be an open source license.\n\n    Probably it will become whatever license Git itself is under,\n    just to make my life easier.\n\nExample:\n~/git-p4c/git-p4c \\\n--server=localhost:1666 \\\n--root=//depot \\\n--repo=/tmp/playground \\\n--user=arafangion \\\n--pass= \\\n--p4=/home/arafangion/perforce/p4 \\\n--max-changes=2 \\\n--branches='\ntrunk=//depot/(trunk)/(.*)\nbranches=//depot/branches/(.*?)/(.*)\n'\n'''\n\nimport datetime\nimport fcntl\nimport marshal\nimport os\nimport subprocess\nimport sys\nimport time\nimport sre\n\ndef main():\n    opts = (\n        '--server',\n        '--user',\n        '--pass',\n        '--allow-case-changes',\n        '--root',\n        '--p4',\n        '--branches',\n        '--repo',\n        '--initial',\n        '--max-changes')\n\n    config = git_config()\n\n    # Now, override configuration if specified:\n    for arg in sys.argv:\n        for opt in opts:\n            if arg.startswith(opt):\n                config[opt[2:]] = arg.split('=', 1)[1]\n\n    config = git_config(config)\n\n    P4C = p4c_Connection(config)\n    GIT = git_Connection(config)\n\n    start = max(int(config['initial'])-1, GIT.latest())\n\n    print 'Downloading Changesets...'\n    c = 0\n    t = time.time()\n    for cs in P4C.changesets(start):\n        if c != 0:\n            print 'Processing:', cs.number, 'Avg: ', (time.time()-t)/float(c), ' at', datetime.datetime.today().ctime(),\n        else:\n            print 'Processing:', cs.number,\n        g = GIT.commit(cs)\n        if g is not None:\n            for file in cs.files():\n                if file.is_interesting():\n                    sys.stdout.write('.')\n                    g.add(file)\n            sys.stdout.write('\\n')\n            g.commit()\n        c += 1\n\n        if c >= int(config['max-changes']):\n            break\n\n    print 'Fetch Complete!'\n\ndef git_config(conf=None):\n    if conf is not None:\n        if 'repo' in conf:\n            try:\n                os.mkdir(conf['repo'])\n            except:\n                pass\n            os.chdir(conf['repo'])\n        g = subprocess.Popen(('git', 'init'))\n        g.wait()\n\n        for key in conf:\n            if '\\n' in conf[key]:\n                c = 1\n                for line in conf[key].split('\\n'):\n                    line = line.strip()\n                    if line=='':\n                        continue\n                    p = subprocess.Popen(('git', 'config', 'git-p4c.'+key+'-'+str(c), line))\n                    p.wait()\n                    c += 1\n            else:\n                p = subprocess.Popen(('git', 'config', 'git-p4c.'+key, conf[key]))\n                p.wait()\n    else:\n        conf = {}\n\n    p = subprocess.Popen(('git', 'config', '-l'), stdout=subprocess.PIPE)\n    p.wait()\n    conf = {}\n    for line in p.stdout.readlines():\n        line = line.strip()\n        if line.startswith('git-p4c.'):\n            key, value = line.split('=', 1)\n            key = key[len('git-p4c.'):]\n            if key.split('-')[0] == 'branches':\n                if 'branches' not in conf:\n                    conf['branches'] = []\n                conf['branches'].append(value)\n            else:\n                conf[key] = value\n\n    # Default Values:\n    if 'initial' not in conf:\n        conf['initial'] = '0'\n    if 'max-changes' not in conf:\n        conf['max-changes'] = '999999999'\n\n    return conf\n\nclass git_Connection:\n    def __init__(self, config):\n        self._latest_mark = 1\n        self._latest_changeset = 0\n        self.config = config\n        self._tags = {}\n        cmd = ('git', 'fast-import')\n        self._fast_import = subprocess.Popen(cmd,\n            bufsize=0,\n            stdin=subprocess.PIPE,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE)\n        self._prev_p4changeset = None\n\n        # Now, determine the current heads:\n        g = subprocess.Popen(('git', 'tag'),\n            stdout=subprocess.PIPE)\n        heads = {}\n        for line in g.stdout.readlines():\n            line = line.strip()\n            branch, number = unformat_tag(config, line)\n            if branch not in heads:\n                heads[branch] = [line, number]\n            if heads[branch][1] < number:\n                heads[branch][1] = number\n        max = 0\n        for head in heads:\n            if max < heads[head][1]:\n                max = heads[head][1]\n            tag = 'refs/tags/'+heads[head][0]\n            t = open('.git/'+tag, 'rb')\n            committish = t.read().strip()\n            self._record_tag(\n                format_tag(\n                    self.config, head, heads[head][1]),\n                committish)\n        self._latest_changeset = max\n        self._heads = heads\n\n    def _record_tag(self, tag, committish):\n        self._tags[tag] = committish\n    def tag_sha1(self, tag):\n        return self._tags[tag]\n    def heads(self):\n        return self._heads\n    def latest(self):\n        'Returns the latest perforce changeset'\n        return self._latest_changeset\n    def commit(self, p4changeset):\n        return git_Commit(self, p4changeset)\n    def next_mark(self):\n        'TODO: Ensure that the latest mark in the marks file is used as the starting point.'\n        self._latest_mark += 1\n        return self._latest_mark\n\nclass git_Commit:\n    def __init__(self, connection, commit):\n        self._con = connection\n        self._commit = commit\n        self._files = {}\n    def add(self, p4file):\n        if not self._files.has_key(p4file.branch()):\n            self._files[p4file.branch()] = []\n        self._files[p4file.branch()].append(p4file)\n\n        if not p4file.action in ('delete', 'purge'):\n            p4file.mark = self._con.next_mark()\n            self._write('blob\\nmark :%(mark)d\\ndata %(size)d\\n' % {\n                'mark':p4file.mark,\n                'size':p4file.size})\n\n            data = 'foo'\n            while data != '':\n                try:\n                    data = p4file.read(1024)\n                    self._write(data)\n                except:\n                    time.sleep(0.1)\n                    data = 'foo'\n        p4file.close_files()\n    def _write(self, s):\n        self._con._fast_import.stdin.write(s)\n    def commit(self):\n        self._mark = self._con.next_mark()\n        mark = self._mark\n        for branch in self._files.keys():\n            if branch in self._con.heads():\n                from_tag = format_tag(self._con.config, branch, self._con.heads()[branch][1])\n            else:\n                from_tag = None\n            self._con.heads()[branch] = [format_tag(self._con.config, branch, self._commit.number), self._commit.number]\n            from_branch = self._files[branch][0].orig_branch()\n            self._write(\n'''commit %(ref)s\nmark :%(mark)d\ncommitter %(name)s <%(email)s> %(when)d +0000\ndata %(length)d\n%(message)s\n''' % {'ref':'refs/heads/'+branch,\n                'mark':mark, \n                'name':self._commit.author(),\n                'email':self._commit.email(),\n                'when':self._commit.time(),\n                'length':len(self._commit.commit_msg()),\n                'message':self._commit.commit_msg()})\n            if branch != from_branch:\n                self._write(\n                    'from %(from)s\\n' %\n                    {'from':'refs/heads/'+from_branch})\n            elif from_tag is not None:\n                self._write(\n                    'from %(from)s\\n' %\n                    {'from':self._con.tag_sha1(from_tag)})\n\n            for file in self._files[branch]:\n                if file.action in ('add', 'edit', 'integrate', 'branch'):\n                    self._c_add(file)\n                elif file.action in ('delete', 'purge'):\n                    self._c_delete(file)\n                else:\n                    print 'Unhandled action:', file\n\n            tagname = format_tag(self._con.config, branch, self._commit.number)\n            self._write(\n'''tag %(tagname)s\nfrom %(committish)s\ntagger %(name)s <%(email)s> %(when)d +0000\ndata 0\n''' % {\n            'tagname':tagname,\n            'committish':':'+str(mark),\n            'name':self._commit.author(),\n            'email':self._commit.email(),\n            'when':self._commit.time()})\n            self._con._record_tag(tagname, ':'+str(mark))\n    def _c_add(self, file):\n        self._write(\n            '''M 100644 :%(mark)d %(path)s\\n''' % {\n                'path':file.name(),\n                'mark':file.mark})\n    def _c_delete(self, file):\n        self._write('D %(path)s\\n' % {'path':file.name()})\n\nclass p4c_Connection:\n    def __init__(self, details):\n        self._p4_exe = details['p4']\n        self._p4port = details['server']\n        self.config = details\n        self._users = None\n    def _p4(self, args):\n        return subprocess.Popen(\n            (self._p4_exe,)+args,\n            bufsize=0,\n            stdin=subprocess.PIPE,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n            env={'P4PORT':self._p4port,'P4PASSWD':self.config['pass'],'P4USER':self.config['user']},\n            close_fds=True)\n    def user(self, username):\n        if self._users is None:\n            p = self._p4(('-G', 'users'))\n            try:\n                self._users = {}\n                while True:\n                    d = marshal.load(p.stdout)\n                    self._users[username] = {}\n                    self._users[username]['email'] = d['Email']\n                    self._users[username]['name'] = d['FullName']\n            except EOFError, e:\n                pass\n        try:\n            return self._users[username]\n        except:\n            return {'email':'Not A Current P4 User', 'name':username}\n\n    def changesets(self, start):\n        if self.config['root'][-1] != '/':\n            self.config['root'] += '/'\n\n        p = self._p4(('-G', 'changes', '-l', '-t', self.config['root']+'...'))\n\n        try:\n            self._changesets = []\n            while True:\n                d = marshal.load(p.stdout)\n                self._changesets.append((int(d['change']), int(d['time'])))\n        except EOFError, e:\n            pass\n\n        def s(x, y):\n            if x[1] < y[1]: return -1\n            if x[1] > y[1]: return 1\n            return 0\n        self._changesets.sort(s)\n        for change, time in self._changesets:\n            if change > start:\n                yield self._Changeset(self, change, time)\n\n    class _File:\n        def __init__(self, connection, details):\n            self._connection = connection\n            self._details = details\n            self.action = self._details['action']\n            p = self._connection._p4(('-G', 'sizes', self.p4name()))\n            try:\n                if not self.action in ('delete', 'purge'):\n                    self.size = marshal.load(p.stdout)\n                    self.size = int(self.size['fileSize'])\n            except Exception, e:\n                self.size = 0\n                self.action = 'purge'\n            self._p = self._connection._p4(('print', '-q', self.p4name()))\n            self.read = self._p.stdout.read\n\n            oldflags = fcntl.fcntl(self._p.stdout, fcntl.F_GETFL)\n            fcntl.fcntl(self._p.stdout, fcntl.F_SETFL, oldflags|os.O_NONBLOCK)\n\n            try:\n                self._branch_name, self._orig_branch, self._name = on_branch(self._connection.config, self.p4name())\n            except:\n                self._branch_name = None\n                self._orig_branch = None\n                self._name = None\n        def p4name(self):\n            return '//'+self._details['file']+'#'+self.rev()\n        def branch(self):\n            return self._branch_name\n        def orig_branch(self):\n            return self._orig_branch\n        def name(self):\n            return self._name\n        def is_interesting(self):\n            return self._branch_name is not None\n        def rev(self):\n            return self._details['rev']\n        def tag(self):\n            print self\n            return self._details['tag']\n        def __str__(self):\n            return '\\t'.join([key+' '+self._details[key] for key in self._details.keys()])\n        def __del__(self):\n            if self.read is not None:\n                self.close_files() \n        def close_files(self):\n            self.read = None\n            self._p.stdout.close()\n            self._p.stderr.close()\n            self._p.stdin.close()\n            self._p.wait()\n            del self._p\n\n    class _Changeset:\n        def __init__(self, connection, number, time):\n            self.number = number\n            self._time = time\n            self._connection = connection\n            self._desc = {}\n            self._files = {}\n\n            p = self._connection._p4(('-G', 'describe',  str(self.number)))\n            try:\n                d = marshal.load(p.stdout)\n                for key in d.keys():\n                    if key[-1] in '0123456789':\n                        'Is referring to a particular file.'\n                        num = 0\n                        name = ''\n                        for c in key:\n                            if c in '0123456789':\n                                num *= 10\n                                num += int(c)\n                            else:\n                                name += c\n                        if not self._files.has_key(num):\n                            self._files[num] = {}\n                        'TODO: Determine which branch(es) this file belongs to.'\n                        if name == 'depotFile':\n                            self._files[num]['file'] = d[key][2:]\n                        else:\n                            self._files[num][name] = d[key]\n                        self._files[num][name] = d[key]\n                    else:\n                        self._desc[key] = d[key]\n            except EOFError, e:\n                pass\n        def __str__(self):\n            return 'Changeset: %s Time: %s' % (self.number, self.time)\n        def commit_msg(self):\n            return self._desc['desc']\n        def author(self):\n            return self._connection.user(self._desc['client'])['name']+\" '\"+self._desc['client']+\"'\"\n        def email(self):\n            return self._connection.user(self._desc['client'])['email']\n        def time(self):\n            return self._time\n\n        def files(self):\n            for number in self._files.keys():\n                yield self._connection._File(self._connection, self._files[number])\n\n\n_seen = None\n_tree = None\ndef generate_tree():\n    '''Reads the current git repo and iterates over every branch, reading all files and\n    directories, in order to ensure that file case does not ever change'''\n\n    def get_branches():\n        p = subprocess.Popen(('git', 'branch'), stdout=subprocess.PIPE)\n        p.wait()\n        for line in p.stdout.readlines():\n            yield line.strip()\n    def get_ls_tree(BranchOrSha1, dir=''):\n        '''This function is very recursive,\n        it returns ALL the trees (ie, the directories).'''\n        p = subprocess.Popen(('git', 'ls-tree', BranchOrSha1), stdout=subprocess.PIPE)\n        p.wait()\n        for line in p.stdout.readlines():\n            items = [item.strip() for item in line.strip().split(' ', 2)]\n            last = items[-1]\n            del items[-1]\n            split_items = last.split('\\t', 1)\n            items.append(split_items[0])\n            items.append(dir+split_items[1]+'/')\n\n            if items[1] == 'tree':\n                yield items\n                for item in get_ls_tree(items[2], items[-1]):\n                    yield item\n\n    global _tree\n    _tree = {}\n\n    for branch in get_branches():\n        for items in get_ls_tree(branch):\n            _tree[items[-1][:-1].lower()] = items[-1][:-1].split('/')[-1]\n\ndef mangle_case(config, file):\n    components = file.split('/')\n    for i in range(len(components)-1):\n        part = '/'.join(components[:i+1]).lower()\n        if part in _tree:\n            components[i] = _tree[part]\n        else:\n            _tree['/'.join(components[:i+1])] = components[i]\n\n    file = '/'.join(components)\n    return file\n\ndef on_branch(config, p4_filename):\n    # TODO: Need to change this so that:\n    # * If desired, prevent changes in case, either by simply preventing changes in case,\n    #   or also by using an 'authoritative' perforce changeset.\n    # * Stop using the stupid global for _seen, and consult the repo.\n    #   (Currently it's worse than nothing, because\n    #   it stuffs up the parent commits.)\n    global _seen\n\n    if _tree is None:\n        generate_tree()\n\n    if _seen is None:\n        _seen = {}\n        p = subprocess.Popen(('git', 'branch'), stdout=subprocess.PIPE)\n        for line in p.stdout.readlines():\n            _seen[line.strip()] = None\n        p.wait()\n\n    first=None\n    for b in config['branches']:\n        b, p = b.split('=', 1)\n\n        if first is None:\n            first = b\n        m = sre.match('^'+p+'\\#.*$', p4_filename)\n        if m:\n            branch, file = m.groups()\n            if branch in _seen:\n                return branch, branch, mangle_case(config, file)\n            else:\n                _seen[branch] = None\n                return branch, first, mangle_case(config, file)\n\ndef format_tag(config, branch, number):\n    return branch+'/'+str(number)\n\ndef unformat_tag(config, tag):\n    branch, number = tag.rsplit('/', 1)\n    number = int(number)\n\n    return branch, number\n\nif __name__ == '__main__':\n    main()\n\n"},{"id":"97873","messageId":"m38wqiewaq.fsf@localhost.localdomain","threadId":"16653","inReplyTo":"1228818317.5504.23.camel@localhost","subject":"Re: New script to convert p4 repositories to git - git-p4c version 1.","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-12-14T13:11:31Z","receivedAt":"2008-12-14T13:11:31Z","isPatch":false,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"John Chapman <thestar@fussycoder.id.au> writes:\n\n> It is called git-p4c, because 'git-p4' was taken, and I intended to\n> write it in C++.  I may still rewrite it in C++ if it is found\n> neccessary to use it on windows. (The Perforce C++ ABI will remove the\n> need to fork so many processes), but I won't be doing that before I\n> implement the write to perforce support.\n> \n> Consider this to be experimental, not yet worthy of a version number.\n\nDo you feel it would be worth adding it below git-p4 (and other\nPerforce importers) on git wiki page:\n  http://git.or.cz/gitwiki/InterfacesFrontendsAndTools\n\nOr would you rather wait a bit to at least version 0.1?\n-- \nJakub Narebski\nPoland\nShadeHawk on #git\n"},{"id":"97993","messageId":"20081215193058.GA5098@osc.edu","threadId":"16653","inReplyTo":"1228818317.5504.23.camel@localhost","subject":"Re: New script to convert p4 repositories to git - git-p4c version 1.","fromName":"Pete Wyckoff","fromEmail":"pw@padd.com","sentAt":"2008-12-15T19:30:58Z","receivedAt":"2008-12-15T19:30:58Z","isPatch":false,"sender":{"key":"pw@padd.com","avatar":null},"body":"thestar@fussycoder.id.au wrote on Tue, 09 Dec 2008 21:25 +1100:\n> I couldn't use git-p4 on my system because I kept running out of memory,\n> and I didn't like the workflow it imposed.\n> Also, it had various other issues with the repo I was trying to use,\n> mainly because it is not an ideal repository, however those are\n> (generally) the fault of the particular repo I was using, and not\n> git-p4. (Which is an excellent script by itself).\n> \n> This script is severely crippled in that it doesn't (yet) allow one to\n> contribute changesets back to perforce, however it manages to read from\n> perforce with:\n> * No need to rebase.\n> * Mangling of file names. (Especially with regards to case sensitivity).\n> * Tagging of revisions with the perforce changesets.\n> * Ability to handle branches with spaces in the name.\n> * Ability to pretend that perforce doesn't exist. (That's the plan,\n> anyway).\n> * Be extremely memory efficient. It does NOT require as much memory as\n> does git-p4, even when the size of the change is large.\n> * Be easy to manually modify the repository, particularly if bad things\n> happen.\n\nI like how your script imports one change at a time, as the initial\nimport using git-p4 here does indeed get close to exhasting virtual\nmemory, but I'm running into a different limitation with p4c.\n\nThe command:\n\n    p4 -G changes -l -t\n\nis adminstratively limited to a paltry six-digit number, and\nproduces only an error message.\n\nThe other feature I need is the ability to use a client\nspecification.  We merge together 40-odd different chunks of //depot\ninto a single checked-out client, and use some other number of\n\"-//depot/...\" rules to exclude some parts of the full depot.\n\nIf I hack p4c to limit the changes with \"-m 10\" or so, then things\nare a bit better in that I get two objects (changesets) but no\ndiffs.  Had to hack the on_branch() code somewhat, in that no form\nof --branches seemed to produce an \"interesting\" changeset by your\ndefinition.  Could be my lack of understanding here.\n\nIf you think you want to handle client specifications, and can think\nof a way around the \"p4 changes\" limitation, I'll be happy to poke\nat your next version.  Perhaps I'm not in your target audience,\nthough.  I don't necessarily need to have a full git history of the\nentire p4, but this seems to be a fundamental part of your approach.\n\n\t\t-- Pete\n"},{"id":"98011","messageId":"1229377952.18839.11.camel@therock.nsw.bigpond.net.au","threadId":"16653","inReplyTo":"20081215193058.GA5098@osc.edu","subject":"Re: New script to convert p4 repositories to git - git-p4c version 1.","fromName":"John Chapman","fromEmail":"thestar@fussycoder.id.au","sentAt":"2008-12-15T21:52:32Z","receivedAt":"2008-12-15T21:52:32Z","isPatch":false,"sender":{"key":"thestar@fussycoder.id.au","avatar":null},"body":"On Mon, 2008-12-15 at 14:30 -0500, Pete Wyckoff wrote:\n<snip>\n> I'm running into a different limitation with p4c.\n> \n> The command:\n> \n>     p4 -G changes -l -t\n> \n> is adminstratively limited to a paltry six-digit number, and\n> produces only an error message.\nHmm, I'm not sure how to resolve this, the repo I use this with so far\nonly uses 6 digit numbers, will have to review the p4 client documents\non this.\n> \n> The other feature I need is the ability to use a client\n> specification.  We merge together 40-odd different chunks of //depot\n> into a single checked-out client, and use some other number of\n> \"-//depot/...\" rules to exclude some parts of the full depot.\n> \n> If I hack p4c to limit the changes with \"-m 10\" or so, then things\n> are a bit better in that I get two objects (changesets) but no\n> diffs.  Had to hack the on_branch() code somewhat, in that no form\n> of --branches seemed to produce an \"interesting\" changeset by your\n> definition.  Could be my lack of understanding here.\nModifying the on_branch code is the right place to do what you want,\nhowever I was hoping that specifying your 'interesting' branches by\nregexp would be sufficient?  All those places that do not get matched\nare effectively ignored and do not become part of the git repo.\n\nThere is a bug in which the case of the branch name sometimes seems to\nchange in my repo, and thus my newer version always lower-cases the\nnames.\n\nDo you need to use a client spec here, or is it possible to just use\nregexps?  I'd like to try and avoid the need to parse the clientspec,\nthat's all.\n\n> If you think you want to handle client specifications, and can think\n> of a way around the \"p4 changes\" limitation, I'll be happy to poke\n> at your next version.  Perhaps I'm not in your target audience,\n> though.  I don't necessarily need to have a full git history of the\n> entire p4, but this seems to be a fundamental part of your approach.\n\nI'm not sure about client specifications, but I definetly do want to\nthink of a way around the p4 changes limitation.\nI'll get a new version out to you soon (Which includes several bug\nfixes), however before I go, I should mention that a deliberate design\ndecision was to allow the git administrator maximum flexibility.\n\nThe script does try to import the full history, however it starts that\nhistory only at the most recent change, which is defined as a tag.\n\nThus, if your main branch is 'trunk', then you should be able to\nmanually import your changes, tag it as 'trunk/102388', to indicate\nthat's p4's revision 102388 of trunk, and the script should then begin\nchecking out changeset 102389.  I haven't had a chance to test that\ntheory yet, but it should work.\n\nThankyou for your valuable feedback.\n\n> \n> \t\t-- Pete\n> --\n> To unsubscribe from this list: send the line \"unsubscribe git\" in\n> the body of a message to majordomo@vger.kernel.org\n> More majordomo info at  http://vger.kernel.org/majordomo-info.html\n"},{"id":"98143","messageId":"20081217140419.GA25761@osc.edu","threadId":"16653","inReplyTo":"1229377952.18839.11.camel@therock.nsw.bigpond.net.au","subject":"Re: New script to convert p4 repositories to git - git-p4c version 1.","fromName":"Pete Wyckoff","fromEmail":"pw@padd.com","sentAt":"2008-12-17T14:04:19Z","receivedAt":"2008-12-17T14:04:19Z","isPatch":false,"sender":{"key":"pw@padd.com","avatar":null},"body":"thestar@fussycoder.id.au wrote on Tue, 16 Dec 2008 08:52 +1100:\n> On Mon, 2008-12-15 at 14:30 -0500, Pete Wyckoff wrote:\n> Modifying the on_branch code is the right place to do what you want,\n> however I was hoping that specifying your 'interesting' branches by\n> regexp would be sufficient?  All those places that do not get matched\n> are effectively ignored and do not become part of the git repo.\n[..]\n> Do you need to use a client spec here, or is it possible to just use\n> regexps?  I'd like to try and avoid the need to parse the clientspec,\n> that's all.\n\nThere is code to parse the client spec in git-p4, and I did hack it\nto put the various depot contents in the git tree according to where\nthe spec says they go.  Not a big hassle, and will do the same for\ngit-p4c if needed.  (Not clean enough or tested with non-client-spec\nconfigs to submit upstream for git-p4 though.)\n\n> The script does try to import the full history, however it starts that\n> history only at the most recent change, which is defined as a tag.\n> \n> Thus, if your main branch is 'trunk', then you should be able to\n> manually import your changes, tag it as 'trunk/102388', to indicate\n> that's p4's revision 102388 of trunk, and the script should then begin\n> checking out changeset 102389.  I haven't had a chance to test that\n> theory yet, but it should work.\n\nOkay, this is interesting.  Would like to pursue that approach.\nExcept for the \"manually import\" part.  Maybe bits of git-p4's\nfull-checkout model could be borrowed here?  Anyway, will take a\nlook when you're ready.\n\n\t\t-- Pete\n"}]}