Git for Perforce users

Viewed 23998

I've been using Perforce for a number of years. I'd like to switch to using git for my personal code, but all of the git tutorials that I've seen either assume that you'e a complete source control n00b (which makes them incredibly tedious) or that you're used to svn (which I'm not).

I know p4, and I also understand the idea behind a distributed source control system (so I don't need a sales pitch, thanks). What I'd like is a translation table from p4 command to equivalent git commands, as well as the "can't live without" commands that have no p4 equivalent.

Since I suspect every p4 user uses a different subset of p4, here are some of the things I regularly do in p4 that I'd like to be able to do in git that aren't immediately obvious from the docs I've looked at:

  1. create multiple pending changelists in a single client. (p4 change)
  2. edit a pending changelist. (also p4 change)
  3. see a list of all of my pending changelists (p4 changes -s pending)
  4. list of all of the changed files in my client (p4 opened) or in a pending changelist (p4 describe)
  5. see a diff of a pending changelist (I use a wrapper script for this which uses p4 diff and p4 describe)
  6. for a given file, see which submitted changelists affected which lines (p4 annotate)
  7. for a given file, see a list of the descriptions of the changelists that affected the file (p4 log)
  8. submit a pending changelist (p4 submit -c)
  9. abort a pending changelist (p4 revert)

A lot of these revolve around "changelists". "changelist" is p4 terminology. What's the git equivalent term?

It sounds like branches might be what git users use in place of what p4 calls changelists. A bit confusing, since p4 also has something called a branch though they seem to be only vaguely related concepts. (Though I always thought p4's concept of a branch was pretty weird it is different yet again from the classic RCS concept of a branch.)

Anyway... I'm not sure how to accomplish what I normally do in p4 changelists with git's branches. In p4 I can do something like this:

$ p4 edit a.txt
$ p4 change a.txt
Change 12345 created.

At this point I have a changlist that contains a.txt. I can edit the description and continue working without submitting the changelist. Also, if it turns out that I need to make some changes to some other files, like say a bugfix in some other layer of the code, I can do that in the same client:

$ p4 edit z.txt
$ p4 change z.txt
Change 12346 created.

Now I have two separate changelists in the same client. I can work on these concurrently, and I don't need to do anything to "switch between" them. When it comes time to commit, I can submit them separately:

$ p4 submit -c 12346  # this will submit the changes to z.txt
$ p4 submit -c 12345  # this will submit the changes to a.txt

I can't figure out how to replicate this in git. From my experiments, it doesn't appear that git add is associated with the current branch. As far as I can tell, when I git commit it's going to commit all files that I git add-ed no matter what branch I was in at the time:

$ git init
Initialized empty Git repository in /home/laurence/git-playground/.git/
$ ls
a.txt  w.txt  z.txt
$ git add -A .
$ git commit
 Initial commit.
 3 files changed, 3 insertions(+), 0 deletions(-)
 create mode 100644 a.txt
 create mode 100644 w.txt
 create mode 100644 z.txt
$ vi a.txt z.txt 
2 files to edit
$ git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#   modified:   a.txt
#   modified:   z.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git branch aardvark
$ git checkout aardvark
M   a.txt
M   z.txt
Switched to branch 'aardvark'
$ git add a.txt 
$ git checkout master
M   a.txt
M   z.txt
Switched to branch 'master'
$ git branch zebra
$ git checkout zebra
M   a.txt
M   z.txt
Switched to branch 'zebra'
$ git add z.txt 
$ git status
# On branch zebra
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   a.txt
#   modified:   z.txt
#
$ git checkout aardvark
M   a.txt
M   z.txt
Switched to branch 'aardvark'
$ git status
# On branch aardvark
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   modified:   a.txt
#   modified:   z.txt

In this example, the aardvark and zebra branches seem to contain exactly the same set of changes, and based on the output of git status it appears that doing a commit in either will have the same effect. Am I doing something wrong?

7 Answers

I suffer like you with the lack of the "changelist" concept which is not exactly the same as git branches.

I would write a small script that will create a changelist file with the list of files in that changelist.

Another command to submit just a certain changelist by simply calling git commit -a @change_list_contents.txt and then "git commit"

Hope that helps, Elias

Having used both Perforce and git fairly extensively, there's only one way I can see to get anywhere near Perforce changelists with git.

The first thing to understand is that to correctly implement this functionality in git in such a way that it's a not a complete kluge, e.g. trying to shoehorn it into branches, would require the following change: git would require multiple staging areas for a single branch.

Perforce changelists permit a workflow that simply has no equivalent in git. Consider the following workflow:

Check out a branch
Modify file A and add it to changelist 1
Modify file B and add it to changelist 2

If you try to do this using branches in git you'll wind up with two branches, one of which has the changes to file A, the other has the changes to file B, but no place where you can see the changes to both files A and B at the same time.

The closest approximation I can see is to use git add . -p and then use the 'a' and 'd' sub-commands to select or reject entire files. However that's not quite the same, and the difference here stems from a fundamental disparity in the general modus operandi of the two systems.

Git (and subversion, not that it matters for this discussion) allow a file to be changed without telling anyone about this ahead of time. You just change a file, and then let git sort it all out when you commit the changes. Perforce requires you to actively check out a file before changes are allowed, and it is for this reason that changelists have to exist. In essence, Perforce requires you to add a file to the index before changing it. Hence the necessity for multiple changelists in Perforce, and also the reason why git has no equivalent. It simply doesn't need them.

With Git 2.27 (Q2 2020), "git p4" learned four new hooks and also "--no-verify" option to bypass them (and the existing "p4-pre-submit" hook).

See commit 1ec4a0a, commit 38ecf75, commit cd1e0dc (14 Feb 2020), and commit 4935c45, commit aa8b766, commit 9f59ca4, commit 6b602a2 (11 Feb 2020) by Ben Keene (seraphire).
(Merged by Junio C Hamano -- gitster -- in commit 5f2ec21, 22 Apr 2020)

git-p4: add p4 submit hooks

Signed-off-by: Ben Keene

The git command "commit" supports a number of hooks that support changing the behavior of the commit command.

The git-p4.py program only has one existing hook, "p4-pre-submit".

This command occurs early in the process.

There are no hooks in the process flow for modifying the P4 changelist text programmatically.

Adds 3 new hooks to git-p4.py to the submit option.

The new hooks are:

  • p4-prepare-changelist - Execute this hook after the changelist file has been created.
    The hook will be executed even if the --prepare-p4-only option is set.
    This hook ignores the --no-verify option in keeping with the existing behavior of git commit.

  • p4-changelist - Execute this hook after the user has edited the changelist.
    Do not execute this hook if the user has selected the --prepare-p4-only option.
    This hook will honor the --no-verify, following the conventions of git commit.

  • p4-post-changelist - Execute this hook after the P4 submission process has completed successfully.
    This hook takes no parameters and is executed regardless of the --no-verify option.

It's return value will not be checked.

The calls to the new hooks: p4-prepare-changelist, p4-changelist, and p4-post-changelist should all be called inside the try-finally block.


Before Git 2.28 (Q3 2020), the "--prepare-p4-only" option is supposed to stop after replaying one changeset, but kept going (by mistake?)

See commit 2dfdd70 (12 May 2020) by Ben Keene (seraphire).
(Merged by Junio C Hamano -- gitster -- in commit 7a8fec9, 02 Jun 2020)

git-p4.py: fix --prepare-p4-only error with multiple commits

Signed-off-by: Ben Keene

When using git p4 submit with the --prepare-p4-only option, the program should prepare a single p4 changelist and notify the user that more commits are pending and then stop processing.

A bug has been introduced by the p4-changelist hook feature that causes the program to continue to try and process all pending changelists at the same time.

The function applyCommit returns True when applying the commit was successful and the program should continue.
However, when the optional flag --prepare-p4-only is set, the program should stop after the first application.

Change the logic in the run method for P4Submit to check for the flag --prepare-p4-only after successfully completing the applyCommit method.

If more than 1 commit is pending submission to P4, the method will properly prepare the P4 changelist, however it will still exit the application with an exitcode of 1.

The current documentation does not define what the exit code should be in this condition.

Related