tags:

views:

222

answers:

3

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?

A: 

I don't have enough p4 experience to produce an actual cheat sheet, but there are at least some similarities to fall back on. A p4 "changeset" is a git "commit".

Changes to your local work space get added to the "index" with git add, and the index later gets committed with git commit. So the index is your pending changelist, for all intents and purposes.

You look at changes with git diff and git status, where git diff usually shows changes between the workspace and the index, but git diff --cached shows changes between the index and the repository (= your pending changelist).

For more in depth information, I recommend http://progit.org/book/. Since you know version control in general, you can probably skim a lot of it and extract the git-specific information...

calmh
+8  A: 

I haven't used perforce much so this may not be exactly be a 1:1 translation. Then again distributed source control systems like git and mercurial have a different workflow anyway so there really isn't (and there shouldn't) be a 1:1 translation. Anyway, here goes:

  • Create multiple pending changelists -> Use branches instead. In git branches are light and quick, takes less than a second to create and typically less than two seconds to merge. Don't be afraid of branching and rebase often.

    git branch new-branch-name
    git checkout new-branch-name
    

    Or do it all in one line:

    git checkout -b new-branch-name
    
  • See a list of all pending changelists -> Since the equivalent of multiple pending changelist is multiple branches just view the branches:

    git branch
    

    If you want to view remote branches as well:

    git branch -a
    

    It is considered good practice to immediately delete a branch after a successful merge so you don't have to keep track of which branch are pending to be merged and which have already been merged.

  • List all changed files -> For a single pending "changelist" in a specific branch git has a concept of the index or cache. In order to commit a change you must first add files to this index. This allows you to manually select which group of files represent a single change or to ignore irrelevant files. To see the status of which files are added, or not to this index just do:

    git status
    
  • See a diff of a pending changelist -> There are two parts to this. First to see a diff between the working directory and the index:

    git diff
    

    But if you want to know the diff between what you're typing now and the last commit then you are really asking for a diff between the working directory+index and the HEAD:

    git diff HEAD
    
  • For a given file, see which submitted changelists affected which lines -> This is easy:

    git blame filename
    

    or even better, if you are in a windowing environment:

    git gui blame filename
    

    Git gui takes longer to parse the file (it was written in tcl instead of C) but it has lots of neat features including the ability to "time travel" back into the past by clicking on a commit ID. I only wish they'd implement a feature to "time travel" to the future so I can find out how a given bug will finally be resolved ;-)

  • For a given file, see a list of the descriptions of the changelists that affected the file -> also easy:

    git log filename
    

    But git log is a much more powerful tool than just this. In fact most of my personal scripts piggyback off-of git log to read the repository. Read the man page.

  • Submit a pending changelist -> Also easy:

    git commit
    

See my answer to a previous question to see my typical git workflow: http://stackoverflow.com/questions/3558036/learning-git-need-to-know-if-i-am-on-the-right-track/3558626#3558626

If you follow the workflow I outlined then you'll find tools like gitk to be much more valuable since it allows you to clearly see groups of changes.


Additional answer:

Git is very flexible and there are several ways to do what you describe. The thing to remember is to always start a new branch for each feature you're working on. This means the master branch isn't touched so you can always go back to it to do bug fixes. Working in git one should almost always start with:

git checkout -b new-feature-a

Now you can edit file a.txt. To work concurrently on another feature do:

git checkout master
git checkout -b new-feature-z

Now you can edit file z.txt. To switch back to a.txt:

git checkout new-feature-a

But wait, there are changes to new-feature-z and git won't let you switch branches. At this point you have two choices. The first is the simplest, commit all changes to the current branch:

git add .
git commit
git checkout new-feature-a

This is what I'd recommend. But if you are really not ready to commit the code, you can temporarily stash it:

git stash

Now you can switch to branch new-feature-a. To go back to the code you were working on just pop the stash:

git checkout new-feature-z
git stash pop

When all is done merge back all changes to master:

git merge --no-ff new-feature-a
git merge --no-ff new-feature-z

Because merges are so quick and easy (easy because conflicts are so rare and conflict resolution, when one does happen, not too hard) we use branches in git for everything.

Here's another example of a common use of branches in git that you don't see in other source control tools (except perhaps mercurial):

Need to keep changing your config files to reflect your dev environment? Then use a branch:

git checkout -b dev-config

Now edit your config files in your favourite editor then commit changes:

git add .
git commit

Now every new branch can start from the dev-config branch instead of master:

git checkout dev-config
git checkout -b new-feature-branch

Once you're done remove the edits in dev-config from new-feature-branch using interactive rebase:

git rebase -i master

Delete the commits you don't want then save. Now you have a clean branch without custom config edits. Time to merge back to master:

git checkout master
git merge --no-ff new-feature-branch
# because master have changed, it's a good idea to rebase dev-config:
git checkout dev-config
git rebase master

It should be noted that removing edits with git rebase -i even works when all changes happen in the same file. Git remembers changes, not file content*.

*note: actually, technically not entirely true but as a user that's what it feels like


More additional answer:

So, from you comments it looks like you want to have two branches to exist simultaneously so you can test how the combined code works. Well, this is a good way to illustrate the power and flexibility of branches.

First, a word on the implication of cheap branching and modifiable history on your workflow. When I was using CVS and SVN I was always a bit reluctant to commit. That's because committing unstable code would inevitably f**k up other people's working code. But with git I lost that fear. That's because in git other people won't get my changes until I merge them to master. So now I find myself committing code every 5 lines I write. You don't need perfect foresight to commit. You just need to change your mindset: commit-to-branch==add-to-changeset, merge-to-master==commit-changeset.

So, back to examples. Here's how I would do it. Say you have a branch new-feature-z and you want to test it with new-feature-a. I would just create a new branch to test it:

# assume we are currently in branch new-feature-z
# branch off this branch for testing
git checkout -b feature-z-and-feature-a
# now temporarily merge new-feature-a
git merge --no-ff new-feature-a

Now you can test. If you need to modify something to make feature-z work with feature-a then do so. If so you can merge back the changes to the relevant branch. Use git rebase -i to remove irrelevant changes from the merge.

Alternatively, you can also use git rebase to temporarily change the base of new-feature-z to point to new-feature-a:

# assume we are currently in branch new-feature-z
git rebase new-feature-a

Now the branch history is modified so that new-feature-z will be based off new-feature-a instead of master. Now you can test. Any changes committed in this branch will belong to the branch new-feature-z. If you need to modify new-feature-a just switch back to it and the rebase to get the new changes:

git checkout new-feature-a
# edit code, add, commit etc..
git checkout new-feature-z
git rebase new-feature-a
# now new-feature-z will contain new changes from new-feature-a

When you're done, simply rebase back to master to remove changes from new-feature-a:

# assume we are currently in branch new-feature-z
git rebase master

Don't be afraid to start a new branch. Don't be afraid to start a throwaway branch. Don't be afraid to throw away branches. And since merge==submit and commit==add-to-changeset don't be afraid to commit often. Remember, commit is a developer's ultimate undo tool.

Oh, and another thing, in git deleted branches still exist in your repository. So if you've accidentally deleted something that you later realise is useful after all you can always get it back by searching the history. So don't be afraid to throw away branches.

slebetman
I tried to figure out how to use git branches to accomplish what I do with changelists in p4, but I haven't been successful. I added more details to the question.
Laurence Gonsalves
Does each branch have its own "index", or is there a single index shared between the branches? My experiment seems to suggest the latter, but you say "a specific branch git has a concept of the index or cache" which suggests the former.
Laurence Gonsalves
It's a different tool. You need a different workflow and with that a different mindset and habit. No git doesn't work the way you describe. It's branches and nothing else. But there are very powerful branch manipulation tools. I suggest you consider youself a newbie who doesn't know anything about source control and read the basic tutorials. Consider your current mindset "bad habbit" that you have to be re-educated against in git-land. Sorry..
slebetman
So what do you do in git when you've got two things you want to work on concurrently (and in the same place, so you can test uncommitted changes against each other), but you want to commit them separately? Also, how can you edit a commit message before performing a commit? I can accept that the workflow is different, but you haven't said what the git-equivalent workflow is. Saying these are bad habits sounds an awful lot like trying to pass off a bug as a feature.
Laurence Gonsalves
Read my updated answer. You're still thinking in terms of uncommitted changes when you should be thinking about unmerged branches.
slebetman
The "bad habbit" comment was from reading Joel Spolsky's experience using mercurial: http://www.joelonsoftware.com/items/2010/03/17.html
slebetman
I found http://www.newartisans.com/2008/04/git-from-the-bottom-up.html and on page 22 it basically acknowledges that git can't do what I want, but suggests using "Stacked Git" to get something similar (and actually more powerful). The branching workflow you suggest seems to assume that I have perfect foresight, which I don't. I frequently get part way into a change and then discover that I need to make a separate change for what I'm working on to work. I want both changes in the same workspace so I can test them against each other, and I don't want to commit either in a non-working state.
Laurence Gonsalves
Ah, so you want both changes to exist at the same time. No problem, create a temporary branch to test or (depending on what you want to do) use rebase. Remember, the majority of the effort is to change your thinking and get used to what git can do. Wait, I'll update my answer.
slebetman
Your point about commit-to-branch=add-to-changeset and merge-to-master=commit-changeset is a good one. I was already aware that commits are much more common in a DVCS, but seeing it stated that way makes it much easier to internalize. Unfortunately, it's still much more heavy-weight than what I do in p4 where I can switch back and forth between a and z just by switching buffers in my editor -- no need to commit, switch branches or merge before testing, and no need to "merge changes back" if I need to modify either a or z.
Laurence Gonsalves
Very extensive answer. At what point should this be wikified?
Tim Clemons
A: 

This doesn't answer your question specifically, but I don't know if you are aware that a 2 User, 5 Workspace version of perforce is free to download and use from the perforce website.

This way you can use perforce at home for your personal projects if you wish. The one annoyance is the 5 workspaces which can be a bit limiting, but its pretty incredible to have perforce available for home use.

Toby Allen
That's what I'd been doing, but I'd like to switch to something that's open-source and also used by open-source projects.
Laurence Gonsalves