Skip to content

Commit 801cbd7

Browse files
committed
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
2 parents 864b5c4 + 44e8d26 commit 801cbd7

File tree

4 files changed

+253
-58
lines changed

4 files changed

+253
-58
lines changed

Documentation/git-p4.txt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ will be fetched and consulted first during a 'git p4 sync'. Since
112112
importing directly from p4 is considerably slower than pulling changes
113113
from a git remote, this can be useful in a multi-developer environment.
114114

115+
If there are multiple branches, doing 'git p4 sync' will automatically
116+
use the "BRANCH DETECTION" algorithm to try to partition new changes
117+
into the right branch. This can be overridden with the '--branch'
118+
option to specify just a single branch to update.
119+
115120

116121
Rebase
117122
~~~~~~
@@ -173,9 +178,11 @@ subsequent 'sync' operations.
173178

174179
--branch <branch>::
175180
Import changes into given branch. If the branch starts with
176-
'refs/', it will be used as is, otherwise the path 'refs/heads/'
177-
will be prepended. The default branch is 'master'. If used
178-
with an initial clone, no HEAD will be checked out.
181+
'refs/', it will be used as is. Otherwise if it does not start
182+
with 'p4/', that prefix is added. The branch is assumed to
183+
name a remote tracking, but this can be modified using
184+
'--import-local', or by giving a full ref name. The default
185+
branch is 'master'.
179186
+
180187
This example imports a new remote "p4/proj2" into an existing
181188
git repository:
@@ -287,6 +294,11 @@ These options can be used to modify 'git p4 submit' behavior.
287294
to bypass the prompt, causing conflicting commits to be automatically
288295
skipped, or to quit trying to apply commits, without prompting.
289296

297+
--branch <branch>::
298+
After submitting, sync this named branch instead of the default
299+
p4/master. See the "Sync options" section above for more
300+
information.
301+
290302
Rebase options
291303
~~~~~~~~~~~~~~
292304
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
394406
presence of the p4 branch. Without p4 branches, the same result will
395407
occur with:
396408
----
409+
git init depot
410+
cd depot
397411
git config git-p4.branchList main:branch1
398-
git p4 clone --detect-branches //depot@all
412+
git p4 clone --detect-branches //depot@all .
399413
----
400414

401415

git-p4.py

Lines changed: 107 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -553,29 +553,49 @@ def gitConfigList(key):
553553
_gitConfig[key] = read_pipe("git config --get-all %s" % key, ignore_error=True).strip().split(os.linesep)
554554
return _gitConfig[key]
555555

556-
def p4BranchesInGit(branchesAreInRemotes = True):
556+
def p4BranchesInGit(branchesAreInRemotes=True):
557+
"""Find all the branches whose names start with "p4/", looking
558+
in remotes or heads as specified by the argument. Return
559+
a dictionary of { branch: revision } for each one found.
560+
The branch names are the short names, without any
561+
"p4/" prefix."""
562+
557563
branches = {}
558564

559565
cmdline = "git rev-parse --symbolic "
560566
if branchesAreInRemotes:
561-
cmdline += " --remotes"
567+
cmdline += "--remotes"
562568
else:
563-
cmdline += " --branches"
569+
cmdline += "--branches"
564570

565571
for line in read_pipe_lines(cmdline):
566572
line = line.strip()
567573

568-
## only import to p4/
569-
if not line.startswith('p4/') or line == "p4/HEAD":
574+
# only import to p4/
575+
if not line.startswith('p4/'):
576+
continue
577+
# special symbolic ref to p4/master
578+
if line == "p4/HEAD":
570579
continue
571-
branch = line
572580

573-
# strip off p4
574-
branch = re.sub ("^p4/", "", line)
581+
# strip off p4/ prefix
582+
branch = line[len("p4/"):]
575583

576584
branches[branch] = parseRevision(line)
585+
577586
return branches
578587

588+
def branch_exists(branch):
589+
"""Make sure that the given ref name really exists."""
590+
591+
cmd = [ "git", "rev-parse", "--symbolic", "--verify", branch ]
592+
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
593+
out, _ = p.communicate()
594+
if p.returncode:
595+
return False
596+
# expect exactly one line of output: the branch name
597+
return out.rstrip() == branch
598+
579599
def findUpstreamBranchPoint(head = "HEAD"):
580600
branches = p4BranchesInGit()
581601
# map from depot-path to branch name
@@ -907,7 +927,8 @@ def __init__(self):
907927
optparse.make_option("--dry-run", "-n", dest="dry_run", action="store_true"),
908928
optparse.make_option("--prepare-p4-only", dest="prepare_p4_only", action="store_true"),
909929
optparse.make_option("--conflict", dest="conflict_behavior",
910-
choices=self.conflict_behavior_choices)
930+
choices=self.conflict_behavior_choices),
931+
optparse.make_option("--branch", dest="branch"),
911932
]
912933
self.description = "Submit changes from git to the perforce depot."
913934
self.usage += " [name of git branch to submit into perforce depot]"
@@ -920,6 +941,7 @@ def __init__(self):
920941
self.isWindows = (platform.system() == "Windows")
921942
self.exportLabels = False
922943
self.p4HasMoveCommand = p4_has_move_command()
944+
self.branch = None
923945

924946
def check(self):
925947
if len(p4CmdList("opened ...")) > 0:
@@ -1656,6 +1678,8 @@ def run(self, args):
16561678
print "All commits applied!"
16571679

16581680
sync = P4Sync()
1681+
if self.branch:
1682+
sync.branch = self.branch
16591683
sync.run([])
16601684

16611685
rebase = P4Rebase()
@@ -2509,13 +2533,6 @@ def getBranchMappingFromGitBranches(self):
25092533
branch = branch[len(self.projectName):]
25102534
self.knownBranches[branch] = branch
25112535

2512-
def listExistingP4GitBranches(self):
2513-
# branches holds mapping from name to commit
2514-
branches = p4BranchesInGit(self.importIntoRemotes)
2515-
self.p4BranchesInGit = branches.keys()
2516-
for branch in branches.keys():
2517-
self.initialParents[self.refPrefix + branch] = branches[branch]
2518-
25192536
def updateOptionDict(self, d):
25202537
option_keys = {}
25212538
if self.keepRepoPath:
@@ -2687,6 +2704,7 @@ def importChanges(self, changes):
26872704
files = self.extractFilesFromCommit(description)
26882705
self.commit(description, files, self.branch,
26892706
self.initialParent)
2707+
# only needed once, to connect to the previous commit
26902708
self.initialParent = ""
26912709
except IOError:
26922710
print self.gitError.read()
@@ -2752,34 +2770,31 @@ def importHeadRevision(self, revision):
27522770
def run(self, args):
27532771
self.depotPaths = []
27542772
self.changeRange = ""
2755-
self.initialParent = ""
27562773
self.previousDepotPaths = []
2774+
self.hasOrigin = False
27572775

27582776
# map from branch depot path to parent branch
27592777
self.knownBranches = {}
27602778
self.initialParents = {}
2761-
self.hasOrigin = originP4BranchesExist()
2762-
if not self.syncWithOrigin:
2763-
self.hasOrigin = False
27642779

27652780
if self.importIntoRemotes:
27662781
self.refPrefix = "refs/remotes/p4/"
27672782
else:
27682783
self.refPrefix = "refs/heads/p4/"
27692784

2770-
if self.syncWithOrigin and self.hasOrigin:
2771-
if not self.silent:
2772-
print "Syncing with origin first by calling git fetch origin"
2773-
system("git fetch origin")
2785+
if self.syncWithOrigin:
2786+
self.hasOrigin = originP4BranchesExist()
2787+
if self.hasOrigin:
2788+
if not self.silent:
2789+
print 'Syncing with origin first, using "git fetch origin"'
2790+
system("git fetch origin")
27742791

2792+
branch_arg_given = bool(self.branch)
27752793
if len(self.branch) == 0:
27762794
self.branch = self.refPrefix + "master"
27772795
if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
27782796
system("git update-ref %s refs/heads/p4" % self.branch)
2779-
system("git branch -D p4");
2780-
# create it /after/ importing, when master exists
2781-
if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
2782-
system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
2797+
system("git branch -D p4")
27832798

27842799
# accept either the command-line option, or the configuration variable
27852800
if self.useClientSpec:
@@ -2796,12 +2811,25 @@ def run(self, args):
27962811
if args == []:
27972812
if self.hasOrigin:
27982813
createOrUpdateBranchesFromOrigin(self.refPrefix, self.silent)
2799-
self.listExistingP4GitBranches()
2814+
2815+
# branches holds mapping from branch name to sha1
2816+
branches = p4BranchesInGit(self.importIntoRemotes)
2817+
2818+
# restrict to just this one, disabling detect-branches
2819+
if branch_arg_given:
2820+
short = self.branch.split("/")[-1]
2821+
if short in branches:
2822+
self.p4BranchesInGit = [ short ]
2823+
else:
2824+
self.p4BranchesInGit = branches.keys()
28002825

28012826
if len(self.p4BranchesInGit) > 1:
28022827
if not self.silent:
28032828
print "Importing from/into multiple branches"
28042829
self.detectBranches = True
2830+
for branch in branches.keys():
2831+
self.initialParents[self.refPrefix + branch] = \
2832+
branches[branch]
28052833

28062834
if self.verbose:
28072835
print "branches: %s" % self.p4BranchesInGit
@@ -2838,13 +2866,21 @@ def run(self, args):
28382866
if p4Change > 0:
28392867
self.depotPaths = sorted(self.previousDepotPaths)
28402868
self.changeRange = "@%s,#head" % p4Change
2841-
if not self.detectBranches:
2842-
self.initialParent = parseRevision(self.branch)
28432869
if not self.silent and not self.detectBranches:
28442870
print "Performing incremental import into %s git branch" % self.branch
28452871

2872+
# accept multiple ref name abbreviations:
2873+
# refs/foo/bar/branch -> use it exactly
2874+
# p4/branch -> prepend refs/remotes/ or refs/heads/
2875+
# branch -> prepend refs/remotes/p4/ or refs/heads/p4/
28462876
if not self.branch.startswith("refs/"):
2847-
self.branch = "refs/heads/" + self.branch
2877+
if self.importIntoRemotes:
2878+
prepend = "refs/remotes/"
2879+
else:
2880+
prepend = "refs/heads/"
2881+
if not self.branch.startswith("p4/"):
2882+
prepend += "p4/"
2883+
self.branch = prepend + self.branch
28482884

28492885
if len(args) == 0 and self.depotPaths:
28502886
if not self.silent:
@@ -2955,8 +2991,21 @@ def run(self, args):
29552991
else:
29562992
# catch "git p4 sync" with no new branches, in a repo that
29572993
# does not have any existing p4 branches
2958-
if len(args) == 0 and not self.p4BranchesInGit:
2959-
die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here.");
2994+
if len(args) == 0:
2995+
if not self.p4BranchesInGit:
2996+
die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here.")
2997+
2998+
# The default branch is master, unless --branch is used to
2999+
# specify something else. Make sure it exists, or complain
3000+
# nicely about how to use --branch.
3001+
if not self.detectBranches:
3002+
if not branch_exists(self.branch):
3003+
if branch_arg_given:
3004+
die("Error: branch %s does not exist." % self.branch)
3005+
else:
3006+
die("Error: no branch %s; perhaps specify one with --branch." %
3007+
self.branch)
3008+
29603009
if self.verbose:
29613010
print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
29623011
self.changeRange)
@@ -2974,6 +3023,14 @@ def run(self, args):
29743023

29753024
self.updatedBranches = set()
29763025

3026+
if not self.detectBranches:
3027+
if args:
3028+
# start a new branch
3029+
self.initialParent = ""
3030+
else:
3031+
# build on a previous revision
3032+
self.initialParent = parseRevision(self.branch)
3033+
29773034
self.importChanges(changes)
29783035

29793036
if not self.silent:
@@ -3006,6 +3063,13 @@ def run(self, args):
30063063
read_pipe("git update-ref -d %s" % branch)
30073064
os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
30083065

3066+
# Create a symbolic ref p4/HEAD pointing to p4/<branch> to allow
3067+
# a convenient shortcut refname "p4".
3068+
if self.importIntoRemotes:
3069+
head_ref = self.refPrefix + "HEAD"
3070+
if not gitBranchExists(head_ref) and gitBranchExists(self.branch):
3071+
system(["git", "symbolic-ref", head_ref, self.branch])
3072+
30093073
return True
30103074

30113075
class P4Rebase(Command):
@@ -3113,17 +3177,15 @@ def run(self, args):
31133177

31143178
if not P4Sync.run(self, depotPaths):
31153179
return False
3116-
if self.branch != "master":
3117-
if self.importIntoRemotes:
3118-
masterbranch = "refs/remotes/p4/master"
3119-
else:
3120-
masterbranch = "refs/heads/p4/master"
3121-
if gitBranchExists(masterbranch):
3122-
system("git branch master %s" % masterbranch)
3123-
if not self.cloneBare:
3124-
system("git checkout -f")
3125-
else:
3126-
print "Could not detect main branch. No checkout/master branch created."
3180+
3181+
# create a master branch and check out a work tree
3182+
if gitBranchExists(self.branch):
3183+
system([ "git", "branch", "master", self.branch ])
3184+
if not self.cloneBare:
3185+
system([ "git", "checkout", "-f" ])
3186+
else:
3187+
print 'Not checking out any branch, use ' \
3188+
'"git checkout -q -b master <branch>"'
31273189

31283190
# auto-set this variable if invoked with --use-client-spec
31293191
if self.useClientSpec_from_options:

t/t9800-git-p4-basic.sh

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,12 @@ test_expect_success 'clone --bare should make a bare repository' '
160160
test_when_finished cleanup_git &&
161161
(
162162
cd "$git" &&
163-
test ! -d .git &&
164-
bare=`git config --get core.bare` &&
165-
test "$bare" = true
163+
test_path_is_missing .git &&
164+
git config --get --bool core.bare true &&
165+
git rev-parse --verify refs/remotes/p4/master &&
166+
git rev-parse --verify refs/remotes/p4/HEAD &&
167+
git rev-parse --verify refs/heads/master &&
168+
git rev-parse --verify HEAD
166169
)
167170
'
168171

0 commit comments

Comments
 (0)