Skip to content

Instantly share code, notes, and snippets.

@ph1ee
Last active March 23, 2020 14:33
Show Gist options
  • Save ph1ee/431eab435e4d5aac374e46ad8a10a8ce to your computer and use it in GitHub Desktop.
Save ph1ee/431eab435e4d5aac374e46ad8a10a8ce to your computer and use it in GitHub Desktop.
Import Perforce changelists into Git p4 branch
#!/usr/bin/python
# For initial import, you may want to sync p4 server to an initial changelist
# manually before running this script:
#
# p4 sync -k "...@<initial_changelist>"
# git init
# git commit -m "Initial commit" --allow-empty
# git checkout -b p4
from __future__ import print_function
import logging
import shlex
import subprocess
import os
import pygit2
from pygit2 import Repository
import sys
import errno
from P4 import P4, P4Exception # Import the module
logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger(__name__)
p4 = P4() # Create the P4 instance
p4.exception_level = 1 # only errors are raised as exceptions
repo = Repository(pygit2.discover_repository(os.getcwd()))
TAG = ".changelist"
def check_p4_ticket():
log.info("Getting ticket status")
ticket = p4.run_login("-s")[0]
user = str(ticket["User"])
expire_in_sec = int(ticket["TicketExpiration"])
if expire_in_sec < 3600:
sys.exit('User {} ticket is about to expire in less than an hour\n'
'Please logout and re-login to P4 by calling "p4 login -a {}"'.format(user, user))
def import_changelist(args, desc):
files = p4.run_sync(*args)[0]
with open(TAG, 'w') as f:
f.write("%s\n" % desc["change"])
repo.index.add_all()
repo.index.write()
treeoid = repo.index.write_tree()
commiter = pygit2.Signature(desc["user"], desc["client"], int(desc["time"]))
message = "{}: {}".format(desc["change"], desc["desc"])
sha = repo.create_commit(repo.head.name, commiter, commiter, message, treeoid, [repo.head.target])
# Split a list into evenly sized chunks
# https://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks
def batch(iterable, n=1):
"""Yield successive n-sized chunks from iterable."""
for i in range(0, len(iterable), n):
yield iterable[i:i + n]
def doit(client):
p4.client = client
clientspec = p4.fetch_client()
stream = clientspec["Stream"]
wsroot = clientspec["Root"]
# https://stackoverflow.com/a/1542818/436813
if wsroot != os.getenv("PWD"):
sys.exit("Not in %s workspace" % p4.client)
wsrealpath = os.path.realpath(wsroot)
cmdline = 'find "%s" -type f ! \( -path "%s/.git/*" -o -path "%s/%s" \) -writable -exec chmod -w "{}" \+' % (
wsrealpath, wsrealpath, wsrealpath, TAG)
if subprocess.Popen(shlex.split(cmdline)).wait() != 0:
sys.exit("error removing write mode from files")
# Initial import
clnum = None
try:
subprocess.Popen(shlex.split("git clean -fdx")).wait()
with open(TAG, 'r') as f:
clnum = f.read().strip()
log.info("Sync'ing changelist %s" % clnum)
args = ["-k", os.path.join(wsroot, "...@%s" % clnum)]
files = p4.run_sync(*args)
except IOError as e:
if e.errno == errno.ENOENT:
for entry in repo.index:
os.remove(entry.path)
args = ["-s", "submitted", "-m1", os.path.join(wsroot, "...#have")]
have = p4.run_changes(*args)[0]
args = ["-s", have["change"]]
desc = p4.run_describe(*args)[0]
clnum = desc["change"]
log.info("Forcibly importing changelist %s" % clnum)
args = ["-f", os.path.join(wsroot, "...@%s" % clnum)]
import_changelist(args, desc)
else:
raise
# Import change(s)
args = ["-s", "submitted", os.path.join(wsroot, "...@>%s" % clnum)]
changes = [c["change"] for c in p4.run_changes(*args)]
changes.reverse()
for chunk in batch(changes, 64):
args = ["-s"]
args.extend(chunk)
describe = p4.run_describe(*args)
for desc in describe:
clnum = desc["change"]
log.info("Importing changelist %s" % clnum)
args = [os.path.join(wsroot, "...@%s" % clnum)]
import_changelist(args, desc)
if __name__ == "__main__": # execute only if run as a script
try: # Catch exceptions with try/except
# What's current branch name?
if repo.head.shorthand != "p4":
sys.exit("Not in p4 branch")
if repo.status():
sys.exit("p4 branch is dirty")
log.info("Connecting to the Perforce server")
p4.connect()
check_p4_ticket()
doit(os.getenv("P4CLIENT", os.path.basename(os.getcwd())))
p4.disconnect()
except P4Exception as e:
strerror = "\n".join(p4.errors)
sys.exit(strerror if strerror else e.value) # Display errors
except IOError as e:
sys.exit(e)
#!/bin/bash
# For initial import, you may want to sync p4 server to an initial changelist
# manually before running this script:
#
# p4 sync -k "...@<initial_changelist>"
# git init
# git commit -m "Initial commit" --allow-empty
# git checkout -b p4
TAG=.changelist
############################################################
ansi() { echo -e "\e[${1}m${@:2}\e[0m"; }
green() { ansi 32 "$@"; }
red() { ansi 31 "$@"; }
info() { green "INFO: $@"; }
die() { red "ERROR: $@"; exit 1; }
############################################################
# If P4CLIENT is null or unset, nothing is substituted, otherwise x is substituted.
[[ -n ${P4CLIENT:+x} ]] || export P4CLIENT=$(basename $(pwd))
[[ "$P4CLIENT" == "$(basename $(pwd))" ]] || die "Not in $P4CLIENT workspace"
root=$(p4 client -o 2>/dev/null | grep "^Root:" | cut -f2)
[[ -d "$root" ]] || die "Client root doesn't exist"
# What's current branch name?
br=$(git symbolic-ref --short HEAD 2>/dev/null)
[[ "$br" == "p4" ]] || die "Not in p4 branch"
# Is current working tree clean?
git diff-index --quiet HEAD -- || die "p4 branch is dirty"
realpath=$(readlink -f "$root")
find "$realpath" -type f ! \( -path "$realpath/.git/*" -o -path "$realpath/$TAG" \) \
-writable -exec chmod -w '{}' \+ || die "Writable files detected"
############################################################
# import <changelist>
import() {
number=$1
args=($(p4 describe -s $number 2>/dev/null | head -1 | cut -d' ' -f4,6-))
name=${args[0]%@*}
from=${args[0]#*@}
date="${args[1]} ${args[2]}"
echo $number > "$TAG" && git add --all && \
p4 describe -s $number | tail -n +2 | cat <(echo "Changelist $number") - | \
git commit --author="$name <$from>" --date="$date" -F -
}
changelist=$(cat "$TAG" 2>/dev/null)
# Initial import
if [[ -z ${changelist:+x} ]]; then
# What's the changelist number of current client root?
changelist=$(p4 changes -s submitted -m1 "$root/...#have" 2>/dev/null | cut -d' ' -f2)
[[ -n ${changelist:+x} ]] || die "Cannot determine highest changelist number in workspace: $root"
# Remove all files (except .git folder)
git rm -rf . 2>/dev/null && git clean -xdf && git reset
info "Forcibly importing changelist $changelist"
p4 sync -f "$root/...@$changelist" && import $changelist
else
# Remove untracked files
git clean -xdf
info "Sync'ing changelist $changelist"
p4 sync -k "$root/...@$changelist"
fi
############################################################
lst=($(p4 changes -s submitted "$root/...@>$changelist" 2>/dev/null | cut -d' ' -f2 | tac))
for changelist in "${lst[@]}"
do
info "Importing changelist $changelist"
p4 sync "$root/...@$changelist" && import $changelist
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment