Last active
March 23, 2020 14:33
-
-
Save ph1ee/431eab435e4d5aac374e46ad8a10a8ce to your computer and use it in GitHub Desktop.
Import Perforce changelists into Git p4 branch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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