Git Transitional Workflow Discussion

rfay's picture

Update 19 December 2010: Our repositories are now updated within 5 minutes after a commit, making this workflow far more usable. All thanks to @neclimdul who bulldogged this to the ground.

Even though the Drupal CVS to Git migration will take a few more months, it's possible to start using git now for nearly everything you used to do. sirkitree challenged me to write down my workflow the other day so we could compare and contrast.

I write this not because it's the only way to do it, but to encourage people to start the git migration and to start a conversation about transitional workflow. And maybe I can get some hints about how to do it better! But I'm quite happy with how this works. When you comment and suggest your alternate approach, please try to point people toward things that will work in the long term in the Git migration.

I encourage you to start migrating to git now. It will make the migration so much easier if you already have the basics working before we get there.

First of all, we already have a git mirror at There are lawyer-notices on the web-view at that say that it might go away (or more likely, have its hashes invalidated) but in my opinion, the risk that this will hurt you or me if it happens is not very high.

The git repositories at are at So if you want to clone the Examples project, it's git clone git:// Cloning core (the drupal project) would be git clone git://

So how do I use this for real work on core and contrib? Easy. Everything but committing works, and committing is something I do less often than all the other parts of the workflow.

I'm not going to show every detail or nuance, just the basics of what I might do on an ordinary day.

Contrib or core patch workflow as non-maintainer (not committing via CVS)

I'll use devel module as a working example:

  1. One time only: cd into sites/all/modules and git clone git://
  2. cd into devel and (if you're not working with CVS HEAD) switch to the correct branch. I'll find out what branches are available and then create a tracking branch (one that can be updated with git pull) for 6.x:
    git branch -r
    git checkout --track origin/6.x-1.x
  3. Now let's say that I have an issue I want to work on, maybe #866608 about a devel_generate fatal in a particular situation. Let's assume there's already a patch in #4 of that issue (by somebody else) that I'm going to work from. I'll create a branch for that, off of my 6.x-1.x branch. git checkout -b devel_generate_866608/04 (There's no magic in the name of that branch. It's for me to figure out why I made the branch.)
  4. Now I'll download and apply the patch. We'll say it's at (I always right-click to copy the link, then wget --directory ~/tmp In the devel directory, I now patch -p0 <~/tmp/dummy_demo.patch
  5. Now I'll commit the patch:
    git add .
    git commit -m "Moshe's #4"
  6. I might now create a new branch for the work I'm about to do on #15:
    git checkout -b devel_generate_866608/15
  7. And now I'll do some work on the module, fixing whatever I need to fix.
  8. Now commit the work I've done:
    git add .
    git commit -m "Fixed up the problem, will upload this as #15"
  9. Now create a patch:
    git diff --no-prefix 6.x-1.x >~/tmp/devel.fatal_866608_15.patch
  10. And upload the patch into the issue queue

Now when I need to do this again and take up work on it, because something has happened in the issue queue, I can either start with my existing branch (or branch from it) or I can do the same basic steps again to use somebody else's patch:

  • Create a branch off of 6.x-1.x (maybe named devel_generate_866608/22 for #22 of that issue).
  • Apply and commit the patch

Everything else is the same. I can compare branches to see exactly what changed:
git diff devel_generate_866608/15
(Note that if other things had been committed to devel in the meantime I might have to go to devel_generate_866608/15 and git merge 6.x-1.x first, so that only the things actually moving in this issue show up)

Maintainer patch workflow

As a module maintainer, it's all the same, except that when the time has come to commit a patch, I make a patch and instead of uploading it in the queue, I apply it to a CVS workarea and commit it using whatever CVS tools. That's the only exposure I have to CVS these days.

True Confessions

I have to confess there's one thing about using as a module maintainer (committer) that doesn't quite work for me yet. The two-hour update of the git repo isn't fast enough when I'm trying to solve some kinds of problems, and I end up using a private git mirror that has a 5-minute refresh. (I use sdboyer's drupal-git-scripts still on a private mirror.). There is an issue open to make updates of much faster and hopefully I can then completely ditch my private mirror.


Lots of smart people have already written on about how to use git in various ways, and there's lots to learn from these articles:


This is GREAT!

sirkitree's picture

Thank you Randy - this makes so much more sense than the process I've been using. My process consisted of a CVS checkout, a personal github repo (which doesn't retain any cvs history) and then the cvs patch applying.

Your branch methodology is fantastic!

I'm totally going to try this out and give more feedback/thoughts here. Thanks again!

This is great, I'm using

Berdir's picture

This is great, I'm using something similiar to work on core issues (Main difference is that only have a single branch per issue)

The only thing I'm not happy with is when it comes to working together with others but I guess that won't improve as long as we don't have phase whatever with a official branch per issue :)

Given that, do you know what the fastest way is to throw everything away in a specific branch and use whatever is in CVS/master? Delete and re-create?

Throwing away a branch and starting over

rfay's picture

I throw away a branch with
git branch -D <branchname>

I create a new branch with the ordinary technique of going to the main branch (master or 6.x-1.x or whatever) and git checkout -b <new branch>.

Not sure if that's exactly what you were asking.

reset the branch

texas-bronius's picture

If you're wanting to continue with the same branch, the quick route is to do a git reset --hard (be in the branch! ;))

btw yes great intro! I hadn't considered subfolder gits to date .. Duh!

Reset without an arg just throws away uncommitted changes

rfay's picture

You could use git reset --hard <backtothebeginning> where "backtothebeginning" is a revision reference of some type to accomplish this, but just git reset --hard would just throw away uncommitted changes. But it would work if you had a tag for the root of the branch or something.

Ok, yes thanks for the very

texas-bronius's picture

Ok, yes thanks for the very important distinction.

Nice write-up. Thanks Randy!

markdorison's picture

Nice write-up. Thanks Randy!

Thanks for the great info!

Bilmar's picture

Thanks for the great info! Now time to do a lot of studying =)

I've been doing nearly the

deviantintegral's picture

I've been doing nearly the same thing using The only thing I do differently is I usually use curl to apply patches since by default it sends the output to stdout:

curl | patch -p0

I also have this in my ~/.bashrc to facilitate cloning:

dclone () { git clone "git://$1.git"; }

Or updated for

dclone () { git clone "git://$1.git"; }


rfay's picture

I like the curl approach a lot. You've just changed the way I work. It was a lot of typing to say --directory ~/tmp, and I have trouble reaching the ~ on my computer :-)

The equivalent with wget is

wget -O - <patch> | patch -p0

For example, in sites/all/modules/examples,

Same here

kotnik's picture

Same here. I've been using to make patches, check out history and all the good stuff. I recommend it.


webchick's picture

I stickied this post, since it's totally awesome. Thanks so much, Randy!

The one adjustment I'd want to make is making "" the official documented place to get stuff from from the command line and wherever else actual "git" is involved. is just the source code browser, which has to be on a different domain so that someone couldn't be sneaky with XSS bugs. Both domains will work, but that's just for usability.

Changed to ""

rfay's picture

Thanks, webchick. I did edit it to change the git repository use to


Very useful, thanks and small glitch

bird-cage's picture

The first link in Resources to "" has as href="" which I think is wrong.

yes, it should be

sirkitree's picture

yes, it should be


rfay's picture

I fixed the link. Thanks.

Stacked Git

scor's picture

This one-branch per patch workflow seems similar to the Stacked Git workflow which many core developers use. I personally haven't use Stacked Git yet as I first wanted to get familiar with Git's native interface, but I'm interested to hear opinion from people who might have used both and could highlight the differences.

Trying To Clone A Module

mermentau's picture

I'm trying to follow this with using TortoiseGit or Git Bash. I put the source to git:// and both give me piwik-6 when I really want piwik-7. I can't find any documentation of how to target the exact files I want. I'm real new to git.

Choose the right branch

rfay's picture

The thing you have to do is to choose the right branch.

Different maintainers use different branches for 7.x. In the case of piwik, 7.x is not "master", as it would be with some projects.

randyfay@falcons:/tmp/piwik$ git branch -r
  origin/HEAD -> origin/master

So it appears here that you want the 7.x branch, so you would:

git checkout --branch origin/7.x-1.x

The issue here is that some maintainers use CVS HEAD (typically "master" in git) and others create a special branch like DRUPAL-7--1 in CVS (origin/7.x-1.x in git) for the latest version. The maintainer of piwik has created a 7.x branch instead, and that's where the stuff you want is.

Got It With TortoiseGit

mermentau's picture

Hit a wall with the command prompt as that's my least favorite tool. With TortoiseGit I was able to clone the master from git:// which as you say was 6.x-1.x which gave me a new repository. I right clicked on the root directory and selected "Switch/Checkout" in TortoiseGit. Then using the branch drop down I selected 7.x-1.x which corresponds to remotes/origin/7.x-1.x This gave me the version that I wanted in my repository.

settings.php & workflow

Jonathan Webb's picture

Something I haven't seen addressed in the drupal+git docs I've read is how developers are managing settings.php (or the sites directory in general). I am trying to find an efficient, easy, and "right" way to do this (but maybe there isn't one?).

This would also apply to adding contrib modules, since (to my knowledge) git submodule is not used to maintain contrib projects. I.E. switching branch from "6.x-issue" to "5x-issue" would ideally switch the devel module to the correct version.

Adding it to a global .gitignore seems like the obvious choice. But this would cause trouble if we use different databases on different branches (since it would not tracked, it wouldn't automatically change). This would require manually maintaining a cache of settings.php files particular to branches if we jump around a lot.

I've tried adding settings.php to branches for tracking and using "git update-index --assume-unchanged ". But I suspect that this would prevent committers from doing "git commit -a". Also patchers would have to manually specify revisions when using diff to create patches (to ignore the revision with the settings commit).

A possible solution I am exploring utilizes configuration directory. I am using MAMP (basic version) for my development environment, and I have set apache to look at the directory "/project" as the document root, accessible at http://localhost:8888. In the example setup below, different snapshots of Drupal will be available as subfolders of the project root.

Initial repository setup for 6.x and 7.x work:

# Similar to rfay's post above:
cd /project
git clone git:// drupal-git
cd drupal-git
git branch --track 6.x-base origin/6.x
git branch --track 7.x-base origin/master

# If you haven't already, make a global .gitignore file
git config --global core.excludesfile ~/.gitignore_global

Setup base 6.x development environment:

# switch to the 6.x branch
cd /project/drupal-git
git checkout 6.x-base

# create a link to the drupal folder in the document root
ln -s /project/drupal-git /project/6x-base

# prime a configuration directory based on the link above
cp -r sites/default sites/8888.localhost.6x-base
mkdir sites/8888.localhost.6x-base/{modules,themes}

# git should ignore it (relative to the drupal-git root)
echo "sites/888.localhost.6x-*" >> ~/.gitignore_global

# create a db
mysqladmin create "drupal-6x-base"

# Now open http://localhost:8888/6x-base in a browser & install
# Install 6.x modules to sites/8888.localhost.6x-base/modules

Likewise, setup base 7.x development environment:

# As above...
cd /project/drupal-git
git checkout 7.x-base
cp -r sites/default sites/8888.localhost.7x-base
mkdir sites/8888.localhost.7x-base/{modules,themes}
echo "sites/888.localhost.7x-*" >> ~/.gitignore_global
mysqladmin create "drupal-7x-base"

# Now open http://localhost:8888/7x-base in a browser & install
# Install 7.x modules to sites/8888.localhost.7x-base/modules

From these respective starting points you could install devel and other "always needed" modules in the respective 6.x or 7.x configuration directory.

To work on a branch for 6.x you could do something like:

#clone database
mysqladmin create "drupal-6x-mybranch" && \
  mysqldump "drupal-6x-base" | mysql "drupal-6x-mybranch"

# clone settings
ln -s /project/drupal-git /project/6x-mybranch
cp -r sites/8888.localhost.6x-base sites/8888.localhost.6x-mybranch

# repoint db in the settings file for mybranch
perl -pi -e "s/drupal-6x-base/drupal-6x-mybranch/g" \

# make the branch from our local 6.x base & checkout immediately
cd /project/drupal-git
git checkout 6.x -b 6x-mybranch

# Now open http://localhost:8888/6x-mybranch in a browser & play

Obviously if you have more control over your apache setup, you could do something similar using vhosts. I'd love to hear some feedback on my setup, and hear what others are doing to manage this process!

settings.php is not normally source controlled

rfay's picture

Regardless of source control environment, sites//settings.php and sites//files are typically added to the .gitignore because they're site-specific things. settings.php is site-specific in many ways; the files directory is user-generated files typically tied to content, so can't really be source controlled with the code. It should be backed up, of course.


bfroehle's picture

Regarding #9:

Another option if you have made a lot of changes is to squash everything into one commit using git rebase -i and then output the patches using git format-patch --no-prefix 6.x-1.x.

This creates a file like 0001-your-commit-message.patch.

The URL for git has changed

joachim's picture

The URL for git has changed from 'projects' to 'project' -- this still needs to be changed in step 1 of the workflow.

Thanks so much for posting this -- extremely helpful and instructive!

Fixed - thanks for letting me

rfay's picture

Fixed - thanks for letting me know.

Is there any way to

joachim's picture

Is there any way to streamline steps 1 and 2?

Checking out HEAD then having to re-checkout to get the 6--1 branch is a few more extra steps than with CVS.

I'm confused. Streamline

sdboyer's picture

I'm confused. Streamline this?

# One time only: cd into sites/all/modules and git clone git://
# cd into devel and (if you're not working with CVS HEAD) switch to the correct branch. I'll find out what branches are available and then create a tracking branch (one that can be updated with git pull) for 6.x:
git branch -r
git checkout --track origin/6.x-1.x

If those are the steps 1 & 2 you're referring to, nowhere in there is master (what used to be CVS HEAD) getting checked out. There's a default local branch that's created which tracks the master branch - and that's checked out automatically by the clone process. That default branch can be set server-side, but that's definitely not something we're going to be implementing right away (probably not until phase 3).

In any case, the step where you git checkout --track origin/6.x-1.x is about as streamlined as it gets. The verbose version to get the same result looks like this:

$ git branch 6.x-1.x
$ git config branch.6.x-1.x.remote origin
$ git checkout 6.x-1.x

It's even a shortened version of the checkout shortcut:

$ git checkout -b 6.x-1.x --track origin/6.x-1.x

I mean streamline this

joachim's picture

I mean streamline this because the following happens when I try these steps:

  1. cd into sites/all/modules and git clone git://

-- gets me HEAD of foo, which is (usually) useless.

  1. git branch -r \n git checkout --track origin/6.x-1.x

-- changes the foo folder I just got in step 1.

In other words, step 1 got me pointless files I had to immediately change.

But from the other comment, steps 1 and 2 can be combined into:

git clone --branch 6.x-1.x git://

which works for me, and can be given in project checkout instructions on :)

From the help page: git clone

sdboyer's picture

From the help page:

git clone --branch 6.x-1.x git://

Wow. Totally forgot about this. So yeah, you can specify the branch that's automatically checked out and set up for tracking at the time you perform the clone.

From the help page: git clone

Berdir's picture

From the help page:

git clone --branch 6.x-1.x git://

Great, thanks! So given how

joachim's picture

Great, thanks!

So given how prevalent a command this is going to be, how can this be aliased?

My attempt doesn't work :/

  dc  = clone --branch $2 git://$1 -

For many projects, that

joachim's picture

For many projects, that says:

warning: Remote branch 6.x-1.x not found in upstream origin, using HEAD instead

You can only clone branches that exist

rfay's picture

So not every project has a 6.x-1.x branch, so you don't be able to clone them.

To see what branches a module has, clone it (without specifying a branch, so you'll get master) and then do

git branch -r

This shouldn't be prevalent

Jonathan Webb's picture

This shouldn't be prevalent for most people, since clone only needs to be run once per project per development environment, not once per branch.

I think if you want to use arguments in your alias you could do something like this:

dc = !sh -c 'git clone -b "$2" "git://$1"' -

Which would allow you to do something like this:

git dc ubercart 6.x-2.x

This gives you a local copy of the repository for ubercart and checks out the branch named 6.x-2.x from your local repository.

Ah -- do git alias params

joachim's picture

Ah -- do git alias params only work if you hop out to sh then? The docs for git I found did not make that clear.

My point is that you're always going to want a particular branch when you check out; hardly ever HEAD.

HEAD (master)'s usage will

sdboyer's picture

HEAD (master)'s usage will likely come down a bit, but I doubt it'll disappear.

As for aliases, here's one way I manage mine. From my ~/.gitconfig

        ci = commit
        st = status
        shb = show-branch
        lc = log ORIG_HEAD.. --stat --no-merges
        llog = log --date-local
        stsh = !CURRENT=$(git symbolic-ref HEAD) && git symbolic-ref HEAD refs/heads/stash && git commit -a -m stashed && git checkout $CURRENT
        serve = !git daemon --reuseaddr --verbose --base-path=. --export-all ./.git
        sh = !git-sh

git submodule

levelos's picture

Anyone have any thoughts on using git's submodule feature for maintaining modules within a Drupal git clone? E.g., I clone the 6.x branch of Drupal and instead of cloning my modules in a separate outside directory, I simply clone them them into the sites/all/modules directory using

git submodule add git://

Lev Tsypin

ThinkShout, Inc. |

I tried this, but ran into

deviantintegral's picture

I tried this, but ran into some significant issues. At the time, there were a few critical modules (such as CCK) that weren't migrating properly (this was on, but I imagine those have been solved. However, the biggest issue is with the atomicity of git tags. Git assumes that all tags are globally unique, which is a problem when nearly every Drupal project will have the same release tags. The only way around it was to track a branch instead of a tag.

At this point, I'm back to drush dl'ing modules and adding them.

This seems odd to me. The

sdboyer's picture

This seems odd to me. The only "globally unique" assumptions I'm aware of git making are that there are no hash collisions - the same hash means the same content. Any human-readable stuff (e.g. tags, branch names) are all just decorations. Are you saying that the parent repository can't handle tracking a tag of the same name in multiple submodules? The parent repo doesn't really track tags or branches at all - it just tracks the SHA1 corresponding to the current HEAD of the submodule (that is, the commit that's currently checked out in the submodule). So I don't really see how there could be a tag namespace issue.

And, see the links in on the cases where submodules are useful.

From simplicity to complexity

rfay's picture

My biggest problem with git submodule is that it takes a very simple workflow (git pull) and turns it into a "Oh, I have to remember the submodules" workflow. I have switched a couple of sandbox codesets to this model, but never have ended up fully happy with it.

My understanding is that git submodule is still in rapid flux, so we may find new possibilities as time goes on.

Thanks for the replies,

levelos's picture

Thanks for the replies, sounds like submodule is not the best approach, for now at least. BTW, in case others find it useful, what I commonly do is keep the module's I'm working on in an outside directory and create symlinks to the modules directory in a Drupal core checkout.

Lev Tsypin

ThinkShout, Inc. |

On submodules, when they're

sdboyer's picture

On submodules, when they're useful and when they aren't, see and .

Thanks Randy et All

manimejia's picture

Great Article and Great discussion!
I use git exclusively, and am constantly looking to improve my workflow. It's a bit rough around the edges, esp in interfacing with cvs. Discussions like this really help.

Project URLs

Josh The Geek's picture

The project URLs now have .git at the end.

I go by Josh The Geek.

As with most git repos, the .git on the end is optional

rfay's picture

The actual repo has a .git on the end, but our setup, like most git setups, makes it optional.

Git repos now update FAST!

rfay's picture

Thanks to @neclimdul, our repositories are now updated within 5 minutes after a commit, making this workflow far more usable. Yippee! I think everybody could move to the git workflow at this point.

Is there any way of getting

joachim's picture

Is there any way of getting nicer branch names, system-wide?

I think '6.x-1.x' is just prone to typos.

Any way og getting

chris.stone's picture

While I do agree that 6.x-1.x is a little cumber some to type and is prone to typos, I have found that using the shell completion available in msysgit, cygwin, and *nix reduces the chance of a typo. Assuming there is no branch above 1.x in the repo you can type git checkout origin/6. then press tab and the reset of the branch name will be filled in for you. if there is a branch above 1.x the shell will complete up to 6.x- allowing you to specify more detail. While I still make the occasional typo using the shell completion has reduced the chance and I think speeds up my work flow as well.

Branch completion on git

joachim's picture

Branch completion on git doesn't work out of the box as far as I know. At least, not on OS X.

git on OSX

chris.stone's picture

From my understanding of the auto completion it's a feature of the shell / terminal itself. Not sure if your using a GUI or if your using a cmd line. Never done dev work on a mac so I do not know if git comes in a terminal flavor for mac. I'm a old shell junkie and have yet to find a operation I can complete faster in a gui versus the shell so I'm a little biased IMHO.

autocompletion is a feature

joachim's picture

autocompletion is a feature of the shell, yes -- but autocompletion of filenames. The shell doesn't know about branches and needs to be told. I've seen things about this in passing but not looked into it. At any rate, branch name completion is not there OOTB.

You can get the

bfroehle's picture

You can get the autocompletion features working pretty easily in bash on OS X, for example, see

Hacking about with files in

joachim's picture

Hacking about with files in /opt does not count as 'easily'... :/

Also, this won't do anything

joachim's picture

Also, this won't do anything for initial checkout.

What I'm asking is can there be aliases on the server side?


webchick's picture

Sorry, this was discussed and decided on months ago. The branch names matching the tarball names make things much clearer for all involved, and furthermore we're only a few weeks away from launch so we're not going to be changing fundamental stuff up like this now.

6.x-1.x is at least /less/ prone to typos than DRUPAL-6--1, so there's that. ;)

Fair enough - I just wondered

joachim's picture

Fair enough - I just wondered if there was a way to set up aliases.

I do think that 6.x-1.x is more typo-prone, simply because it involves so much more skating around the keyboard. I certainly can't type it without having to stop and think, whereas DRUPAL-6--1 I don't.

Yes, we could set up aliases

sdboyer's picture

Yes, we could set up aliases - see git-symbolic-ref - but it's going to take more than a feeling that it's more typo prone to convince me to do it. We've got a clean system now that's the same in the vcs as it is for our packaging system. That's a huge benefit, and it'd be ugly (plus REALLY confusing the longer it goes on) to have all the old branch names there too. And, since branching in git is trivial, so if you do make a typo, it's no biggie to fix.

I wouldn't put 'using the

bfroehle's picture

I wouldn't put 'using the terminal' in the 'easy' category as well. If you want convenience, try something like GitX at or


chris.stone's picture

IMHO using terminal could be put in the easy medium or hard category. It all depends on what your used to or how familiar you are with similar shells. I've got a couple friends that won't let me within 10 feet of there macs cause they know I"ll pop the term open and start looking around and they have no clue what I'm doing. Guess it's a difference between being raised on a shell versus a GUI.

Here's another one... I've

joachim's picture

Here's another one...

I've got a git repo for a project, and I've just made a fix to a module. I'd like to diff against my own repo to make a patch, but the patch file has these:

--- sites/all/modules/contrib/emfield/includes/
+++ sites/all/modules/contrib/emfield/includes/

How do I get diff to output something that's got the correct file paths?

If you used Git diff, that's

Josh The Geek's picture

If you used Git diff, that's normal.

I go by Josh The Geek.

Of course it is. But is there

joachim's picture

Of course it is. But is there a way to change it?


answer is:

git diff --no-prefix  --relative

A trick I've found with this

joachim's picture

A trick I've found with this workflow is to make a symlink in your CVS checkout folder to your git folder, so the commands for creating a patch are:

// in git folder
$ git diff > foo.patch
// in CVS folder
$ patch -p0 < git/foo.patch