1
0
mirror of https://github.com/git/git.git synced 2024-09-23 22:31:19 +02:00

Merge branch 'pw/p4-branch-fixes'

Fix "git p4" around branch handling.

* pw/p4-branch-fixes:
  git p4: fix submit when no master branch
  git p4 test: keep P4CLIENT changes inside subshells
  git p4: fix sync --branch when no master branch
  git p4: fail gracefully on sync with no master branch
  git p4: rearrange self.initialParent use
  git p4: allow short ref names to --branch
  git p4 doc: fix branch detection example
  git p4: clone --branch should checkout master
  git p4: verify expected refs in clone --bare test
  git p4: create p4/HEAD on initial clone
  git p4: inline listExistingP4GitBranches
  git p4: add comments to p4BranchesInGit
  git p4: rearrange and simplify hasOrigin handling
  git p4: test sync/clone --branch behavior
This commit is contained in:
Junio C Hamano 2013-01-21 20:15:44 -08:00
commit 801cbd7c71
4 changed files with 253 additions and 58 deletions

View File

@ -112,6 +112,11 @@ will be fetched and consulted first during a 'git p4 sync'. Since
importing directly from p4 is considerably slower than pulling changes importing directly from p4 is considerably slower than pulling changes
from a git remote, this can be useful in a multi-developer environment. from a git remote, this can be useful in a multi-developer environment.
If there are multiple branches, doing 'git p4 sync' will automatically
use the "BRANCH DETECTION" algorithm to try to partition new changes
into the right branch. This can be overridden with the '--branch'
option to specify just a single branch to update.
Rebase Rebase
~~~~~~ ~~~~~~
@ -173,9 +178,11 @@ subsequent 'sync' operations.
--branch <branch>:: --branch <branch>::
Import changes into given branch. If the branch starts with Import changes into given branch. If the branch starts with
'refs/', it will be used as is, otherwise the path 'refs/heads/' 'refs/', it will be used as is. Otherwise if it does not start
will be prepended. The default branch is 'master'. If used with 'p4/', that prefix is added. The branch is assumed to
with an initial clone, no HEAD will be checked out. name a remote tracking, but this can be modified using
'--import-local', or by giving a full ref name. The default
branch is 'master'.
+ +
This example imports a new remote "p4/proj2" into an existing This example imports a new remote "p4/proj2" into an existing
git repository: git repository:
@ -287,6 +294,11 @@ These options can be used to modify 'git p4 submit' behavior.
to bypass the prompt, causing conflicting commits to be automatically to bypass the prompt, causing conflicting commits to be automatically
skipped, or to quit trying to apply commits, without prompting. skipped, or to quit trying to apply commits, without prompting.
--branch <branch>::
After submitting, sync this named branch instead of the default
p4/master. See the "Sync options" section above for more
information.
Rebase options Rebase options
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
These options can be used to modify 'git p4 rebase' behavior. These options can be used to modify 'git p4 rebase' behavior.
@ -394,8 +406,10 @@ the path elements in the p4 repository. The example above relied on the
presence of the p4 branch. Without p4 branches, the same result will presence of the p4 branch. Without p4 branches, the same result will
occur with: occur with:
---- ----
git init depot
cd depot
git config git-p4.branchList main:branch1 git config git-p4.branchList main:branch1
git p4 clone --detect-branches //depot@all git p4 clone --detect-branches //depot@all .
---- ----

152
git-p4.py
View File

@ -553,29 +553,49 @@ def gitConfigList(key):
_gitConfig[key] = read_pipe("git config --get-all %s" % key, ignore_error=True).strip().split(os.linesep) _gitConfig[key] = read_pipe("git config --get-all %s" % key, ignore_error=True).strip().split(os.linesep)
return _gitConfig[key] return _gitConfig[key]
def p4BranchesInGit(branchesAreInRemotes = True): def p4BranchesInGit(branchesAreInRemotes=True):
"""Find all the branches whose names start with "p4/", looking
in remotes or heads as specified by the argument. Return
a dictionary of { branch: revision } for each one found.
The branch names are the short names, without any
"p4/" prefix."""
branches = {} branches = {}
cmdline = "git rev-parse --symbolic " cmdline = "git rev-parse --symbolic "
if branchesAreInRemotes: if branchesAreInRemotes:
cmdline += " --remotes" cmdline += "--remotes"
else: else:
cmdline += " --branches" cmdline += "--branches"
for line in read_pipe_lines(cmdline): for line in read_pipe_lines(cmdline):
line = line.strip() line = line.strip()
## only import to p4/ # only import to p4/
if not line.startswith('p4/') or line == "p4/HEAD": if not line.startswith('p4/'):
continue
# special symbolic ref to p4/master
if line == "p4/HEAD":
continue continue
branch = line
# strip off p4 # strip off p4/ prefix
branch = re.sub ("^p4/", "", line) branch = line[len("p4/"):]
branches[branch] = parseRevision(line) branches[branch] = parseRevision(line)
return branches return branches
def branch_exists(branch):
"""Make sure that the given ref name really exists."""
cmd = [ "git", "rev-parse", "--symbolic", "--verify", branch ]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, _ = p.communicate()
if p.returncode:
return False
# expect exactly one line of output: the branch name
return out.rstrip() == branch
def findUpstreamBranchPoint(head = "HEAD"): def findUpstreamBranchPoint(head = "HEAD"):
branches = p4BranchesInGit() branches = p4BranchesInGit()
# map from depot-path to branch name # map from depot-path to branch name
@ -907,7 +927,8 @@ class P4Submit(Command, P4UserMap):
optparse.make_option("--dry-run", "-n", dest="dry_run", action="store_true"), optparse.make_option("--dry-run", "-n", dest="dry_run", action="store_true"),
optparse.make_option("--prepare-p4-only", dest="prepare_p4_only", action="store_true"), optparse.make_option("--prepare-p4-only", dest="prepare_p4_only", action="store_true"),
optparse.make_option("--conflict", dest="conflict_behavior", optparse.make_option("--conflict", dest="conflict_behavior",
choices=self.conflict_behavior_choices) choices=self.conflict_behavior_choices),
optparse.make_option("--branch", dest="branch"),
] ]
self.description = "Submit changes from git to the perforce depot." self.description = "Submit changes from git to the perforce depot."
self.usage += " [name of git branch to submit into perforce depot]" self.usage += " [name of git branch to submit into perforce depot]"
@ -920,6 +941,7 @@ class P4Submit(Command, P4UserMap):
self.isWindows = (platform.system() == "Windows") self.isWindows = (platform.system() == "Windows")
self.exportLabels = False self.exportLabels = False
self.p4HasMoveCommand = p4_has_move_command() self.p4HasMoveCommand = p4_has_move_command()
self.branch = None
def check(self): def check(self):
if len(p4CmdList("opened ...")) > 0: if len(p4CmdList("opened ...")) > 0:
@ -1656,6 +1678,8 @@ class P4Submit(Command, P4UserMap):
print "All commits applied!" print "All commits applied!"
sync = P4Sync() sync = P4Sync()
if self.branch:
sync.branch = self.branch
sync.run([]) sync.run([])
rebase = P4Rebase() rebase = P4Rebase()
@ -2509,13 +2533,6 @@ class P4Sync(Command, P4UserMap):
branch = branch[len(self.projectName):] branch = branch[len(self.projectName):]
self.knownBranches[branch] = branch self.knownBranches[branch] = branch
def listExistingP4GitBranches(self):
# branches holds mapping from name to commit
branches = p4BranchesInGit(self.importIntoRemotes)
self.p4BranchesInGit = branches.keys()
for branch in branches.keys():
self.initialParents[self.refPrefix + branch] = branches[branch]
def updateOptionDict(self, d): def updateOptionDict(self, d):
option_keys = {} option_keys = {}
if self.keepRepoPath: if self.keepRepoPath:
@ -2687,6 +2704,7 @@ class P4Sync(Command, P4UserMap):
files = self.extractFilesFromCommit(description) files = self.extractFilesFromCommit(description)
self.commit(description, files, self.branch, self.commit(description, files, self.branch,
self.initialParent) self.initialParent)
# only needed once, to connect to the previous commit
self.initialParent = "" self.initialParent = ""
except IOError: except IOError:
print self.gitError.read() print self.gitError.read()
@ -2752,34 +2770,31 @@ class P4Sync(Command, P4UserMap):
def run(self, args): def run(self, args):
self.depotPaths = [] self.depotPaths = []
self.changeRange = "" self.changeRange = ""
self.initialParent = ""
self.previousDepotPaths = [] self.previousDepotPaths = []
self.hasOrigin = False
# map from branch depot path to parent branch # map from branch depot path to parent branch
self.knownBranches = {} self.knownBranches = {}
self.initialParents = {} self.initialParents = {}
self.hasOrigin = originP4BranchesExist()
if not self.syncWithOrigin:
self.hasOrigin = False
if self.importIntoRemotes: if self.importIntoRemotes:
self.refPrefix = "refs/remotes/p4/" self.refPrefix = "refs/remotes/p4/"
else: else:
self.refPrefix = "refs/heads/p4/" self.refPrefix = "refs/heads/p4/"
if self.syncWithOrigin and self.hasOrigin: if self.syncWithOrigin:
if not self.silent: self.hasOrigin = originP4BranchesExist()
print "Syncing with origin first by calling git fetch origin" if self.hasOrigin:
system("git fetch origin") if not self.silent:
print 'Syncing with origin first, using "git fetch origin"'
system("git fetch origin")
branch_arg_given = bool(self.branch)
if len(self.branch) == 0: if len(self.branch) == 0:
self.branch = self.refPrefix + "master" self.branch = self.refPrefix + "master"
if gitBranchExists("refs/heads/p4") and self.importIntoRemotes: if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
system("git update-ref %s refs/heads/p4" % self.branch) system("git update-ref %s refs/heads/p4" % self.branch)
system("git branch -D p4"); system("git branch -D p4")
# create it /after/ importing, when master exists
if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
# accept either the command-line option, or the configuration variable # accept either the command-line option, or the configuration variable
if self.useClientSpec: if self.useClientSpec:
@ -2796,12 +2811,25 @@ class P4Sync(Command, P4UserMap):
if args == []: if args == []:
if self.hasOrigin: if self.hasOrigin:
createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent) createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
self.listExistingP4GitBranches()
# branches holds mapping from branch name to sha1
branches = p4BranchesInGit(self.importIntoRemotes)
# restrict to just this one, disabling detect-branches
if branch_arg_given:
short = self.branch.split("/")[-1]
if short in branches:
self.p4BranchesInGit = [ short ]
else:
self.p4BranchesInGit = branches.keys()
if len(self.p4BranchesInGit) > 1: if len(self.p4BranchesInGit) > 1:
if not self.silent: if not self.silent:
print "Importing from/into multiple branches" print "Importing from/into multiple branches"
self.detectBranches = True self.detectBranches = True
for branch in branches.keys():
self.initialParents[self.refPrefix + branch] = \
branches[branch]
if self.verbose: if self.verbose:
print "branches: %s" % self.p4BranchesInGit print "branches: %s" % self.p4BranchesInGit
@ -2838,13 +2866,21 @@ class P4Sync(Command, P4UserMap):
if p4Change > 0: if p4Change > 0:
self.depotPaths = sorted(self.previousDepotPaths) self.depotPaths = sorted(self.previousDepotPaths)
self.changeRange = "@%s,#head" % p4Change self.changeRange = "@%s,#head" % p4Change
if not self.detectBranches:
self.initialParent = parseRevision(self.branch)
if not self.silent and not self.detectBranches: if not self.silent and not self.detectBranches:
print "Performing incremental import into %s git branch" % self.branch print "Performing incremental import into %s git branch" % self.branch
# accept multiple ref name abbreviations:
# refs/foo/bar/branch -> use it exactly
# p4/branch -> prepend refs/remotes/ or refs/heads/
# branch -> prepend refs/remotes/p4/ or refs/heads/p4/
if not self.branch.startswith("refs/"): if not self.branch.startswith("refs/"):
self.branch = "refs/heads/" + self.branch if self.importIntoRemotes:
prepend = "refs/remotes/"
else:
prepend = "refs/heads/"
if not self.branch.startswith("p4/"):
prepend += "p4/"
self.branch = prepend + self.branch
if len(args) == 0 and self.depotPaths: if len(args) == 0 and self.depotPaths:
if not self.silent: if not self.silent:
@ -2955,8 +2991,21 @@ class P4Sync(Command, P4UserMap):
else: else:
# catch "git p4 sync" with no new branches, in a repo that # catch "git p4 sync" with no new branches, in a repo that
# does not have any existing p4 branches # does not have any existing p4 branches
if len(args) == 0 and not self.p4BranchesInGit: if len(args) == 0:
die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here."); if not self.p4BranchesInGit:
die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here.")
# The default branch is master, unless --branch is used to
# specify something else. Make sure it exists, or complain
# nicely about how to use --branch.
if not self.detectBranches:
if not branch_exists(self.branch):
if branch_arg_given:
die("Error: branch %s does not exist." % self.branch)
else:
die("Error: no branch %s; perhaps specify one with --branch." %
self.branch)
if self.verbose: if self.verbose:
print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths), print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
self.changeRange) self.changeRange)
@ -2974,6 +3023,14 @@ class P4Sync(Command, P4UserMap):
self.updatedBranches = set() self.updatedBranches = set()
if not self.detectBranches:
if args:
# start a new branch
self.initialParent = ""
else:
# build on a previous revision
self.initialParent = parseRevision(self.branch)
self.importChanges(changes) self.importChanges(changes)
if not self.silent: if not self.silent:
@ -3006,6 +3063,13 @@ class P4Sync(Command, P4UserMap):
read_pipe("git update-ref -d %s" % branch) read_pipe("git update-ref -d %s" % branch)
os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation)) os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
# Create a symbolic ref p4/HEAD pointing to p4/<branch> to allow
# a convenient shortcut refname "p4".
if self.importIntoRemotes:
head_ref = self.refPrefix + "HEAD"
if not gitBranchExists(head_ref) and gitBranchExists(self.branch):
system(["git", "symbolic-ref", head_ref, self.branch])
return True return True
class P4Rebase(Command): class P4Rebase(Command):
@ -3113,17 +3177,15 @@ class P4Clone(P4Sync):
if not P4Sync.run(self, depotPaths): if not P4Sync.run(self, depotPaths):
return False return False
if self.branch != "master":
if self.importIntoRemotes: # create a master branch and check out a work tree
masterbranch = "refs/remotes/p4/master" if gitBranchExists(self.branch):
else: system([ "git", "branch", "master", self.branch ])
masterbranch = "refs/heads/p4/master" if not self.cloneBare:
if gitBranchExists(masterbranch): system([ "git", "checkout", "-f" ])
system("git branch master %s" % masterbranch) else:
if not self.cloneBare: print 'Not checking out any branch, use ' \
system("git checkout -f") '"git checkout -q -b master <branch>"'
else:
print "Could not detect main branch. No checkout/master branch created."
# auto-set this variable if invoked with --use-client-spec # auto-set this variable if invoked with --use-client-spec
if self.useClientSpec_from_options: if self.useClientSpec_from_options:

View File

@ -160,9 +160,12 @@ test_expect_success 'clone --bare should make a bare repository' '
test_when_finished cleanup_git && test_when_finished cleanup_git &&
( (
cd "$git" && cd "$git" &&
test ! -d .git && test_path_is_missing .git &&
bare=`git config --get core.bare` && git config --get --bool core.bare true &&
test "$bare" = true git rev-parse --verify refs/remotes/p4/master &&
git rev-parse --verify refs/remotes/p4/HEAD &&
git rev-parse --verify refs/heads/master &&
git rev-parse --verify HEAD
) )
' '

View File

@ -27,14 +27,102 @@ test_expect_success 'clone no --git-dir' '
test_must_fail git p4 clone --git-dir=xx //depot test_must_fail git p4 clone --git-dir=xx //depot
' '
test_expect_success 'clone --branch' ' test_expect_success 'clone --branch should checkout master' '
git p4 clone --branch=refs/remotes/p4/sb --dest="$git" //depot && git p4 clone --branch=refs/remotes/p4/sb --dest="$git" //depot &&
test_when_finished cleanup_git && test_when_finished cleanup_git &&
( (
cd "$git" && cd "$git" &&
git ls-files >files && git rev-parse refs/remotes/p4/sb >sb &&
test_line_count = 0 files && git rev-parse refs/heads/master >master &&
test_path_is_file .git/refs/remotes/p4/sb test_cmp sb master &&
git rev-parse HEAD >head &&
test_cmp sb head
)
'
test_expect_success 'sync when no master branch prints a nice error' '
test_when_finished cleanup_git &&
git p4 clone --branch=refs/remotes/p4/sb --dest="$git" //depot@2 &&
(
cd "$git" &&
test_must_fail git p4 sync 2>err &&
grep "Error: no branch refs/remotes/p4/master" err
)
'
test_expect_success 'sync --branch builds the full ref name correctly' '
test_when_finished cleanup_git &&
(
cd "$git" &&
git init &&
git p4 sync --branch=b1 //depot &&
git rev-parse --verify refs/remotes/p4/b1 &&
git p4 sync --branch=p4/b2 //depot &&
git rev-parse --verify refs/remotes/p4/b2 &&
git p4 sync --import-local --branch=h1 //depot &&
git rev-parse --verify refs/heads/p4/h1 &&
git p4 sync --import-local --branch=p4/h2 //depot &&
git rev-parse --verify refs/heads/p4/h2 &&
git p4 sync --branch=refs/stuff //depot &&
git rev-parse --verify refs/stuff
)
'
# engages --detect-branches code, which will do filename filtering so
# no sync to either b1 or b2
test_expect_success 'sync when two branches but no master should noop' '
test_when_finished cleanup_git &&
(
cd "$git" &&
git init &&
git p4 sync --branch=refs/remotes/p4/b1 //depot@2 &&
git p4 sync --branch=refs/remotes/p4/b2 //depot@2 &&
git p4 sync &&
git show -s --format=%s refs/remotes/p4/b1 >show &&
grep "Initial import" show &&
git show -s --format=%s refs/remotes/p4/b2 >show &&
grep "Initial import" show
)
'
test_expect_success 'sync --branch updates specific branch, no detection' '
test_when_finished cleanup_git &&
(
cd "$git" &&
git init &&
git p4 sync --branch=b1 //depot@2 &&
git p4 sync --branch=b2 //depot@2 &&
git p4 sync --branch=b2 &&
git show -s --format=%s refs/remotes/p4/b1 >show &&
grep "Initial import" show &&
git show -s --format=%s refs/remotes/p4/b2 >show &&
grep "change 3" show
)
'
# allows using the refname "p4" as a short name for p4/master
test_expect_success 'clone creates HEAD symbolic reference' '
git p4 clone --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
git rev-parse --verify refs/remotes/p4/master >master &&
git rev-parse --verify p4 >p4 &&
test_cmp master p4
)
'
test_expect_success 'clone --branch creates HEAD symbolic reference' '
git p4 clone --branch=refs/remotes/p4/sb --dest="$git" //depot &&
test_when_finished cleanup_git &&
(
cd "$git" &&
git rev-parse --verify refs/remotes/p4/sb >sb &&
git rev-parse --verify p4 >p4 &&
test_cmp sb p4
) )
' '
@ -138,9 +226,11 @@ test_expect_success 'clone --use-client-spec' '
View: //depot/sub/... //client2/bus/... View: //depot/sub/... //client2/bus/...
EOF EOF
) && ) &&
P4CLIENT=client2 &&
test_when_finished cleanup_git && test_when_finished cleanup_git &&
git p4 clone --dest="$git" --use-client-spec //depot/... && (
P4CLIENT=client2 &&
git p4 clone --dest="$git" --use-client-spec //depot/...
) &&
( (
cd "$git" && cd "$git" &&
test_path_is_file bus/dir/f4 && test_path_is_file bus/dir/f4 &&
@ -153,6 +243,7 @@ test_expect_success 'clone --use-client-spec' '
cd "$git" && cd "$git" &&
git init && git init &&
git config git-p4.useClientSpec true && git config git-p4.useClientSpec true &&
P4CLIENT=client2 &&
git p4 sync //depot/... && git p4 sync //depot/... &&
git checkout -b master p4/master && git checkout -b master p4/master &&
test_path_is_file bus/dir/f4 && test_path_is_file bus/dir/f4 &&
@ -160,6 +251,31 @@ test_expect_success 'clone --use-client-spec' '
) )
' '
test_expect_success 'submit works with no p4/master' '
test_when_finished cleanup_git &&
git p4 clone --branch=b1 //depot@1,2 --destination="$git" &&
(
cd "$git" &&
test_commit submit-1-branch &&
git config git-p4.skipSubmitEdit true &&
git p4 submit --branch=b1
)
'
# The sync/rebase part post-submit will engage detect-branches
# machinery which will not do anything in this particular test.
test_expect_success 'submit works with two branches' '
test_when_finished cleanup_git &&
git p4 clone --branch=b1 //depot@1,2 --destination="$git" &&
(
cd "$git" &&
git p4 sync --branch=b2 //depot@1,3 &&
test_commit submit-2-branches &&
git config git-p4.skipSubmitEdit true &&
git p4 submit
)
'
test_expect_success 'kill p4d' ' test_expect_success 'kill p4d' '
kill_p4d kill_p4d
' '