What's the easiest way to commit and push a single file while leaving other modifications alone?
Asked Answered
H

6

72

I'm relatively new to Mercurial and my team is trying it out right now as a replacement for Subversion.

How can I commit and push a single file out to another repository while leaving other modifications in my working directory uncommitted (or at least not pushed to the other repository)?

This happens for us with database migrations. We want to commit the migration to source control so a DBA can view and edit it while we're working on the code modifications to go along with that database migration. The changes aren't yet ready to go so we don't want to push all of them out.

In subversion, I'd simply do:

svn add my_migration.sql  
# commit only the migration, but not the other files I'm working on
svn commit -m "migration notes" my_mygration.sql

and continue working locally.

This doesn't work with mercurial as when I'm pushing it out to the other repository, if there are changes to it that I haven't pulled down, it wants me to pull them down, merge them, and commit that merge to the repository. Commits after a merge don't allow you to omit files so it forces you to commit everything in your local repository.

The easiest thing that I can figure out is to commit the file to my local repository, clone my local repository, fetch any new changes from the actual repository, merge them and commit that merge, and them push my changes out.

hg add my_migration.sql 
hg commit -m "migration notes" my_migration.sql 
cd ..
hg clone project project-clone
cd project-clone
hg fetch http://hg/project
hg push  http://hg/project

This works, but it feels like I'm missing something easier, some way to tell mercurial to ignore the files already in my working directory, just do the merge and send the files along. I suspect mercurial queues can do this, but I don't fully grok mq yet.

Helenahelene answered 24/9, 2008 at 3:33 Comment(2)
this is one feature that I really value in git (i use it all the time), and would make it hard for me to switch...Hardison
This isn't actually how I'd do things now that I've learned a lot more about hg. Now I'd commit the changes locally, update back to the previous revision and do my changes there and "hg push --rev ." to nudge out only the current branch. Then update back to the other work and continue there. If I decided I didn't want that work anymore, I'd just "hg strip" it out. Much easier and you don't need to worry about rejected hunks of files, everything is tracked and in source control.Helenahelene
M
31

There's a Mercurial feature that implements shelve and unshelve commands, which give you an interactive way to specify changes to store away until a later time: Shelve.

Then you can hg shelve and hg unshelve to temporarily store changes away. It lets you work at the "patch hunk" level to pick and choose the items to shelve away. It didn't appear to shelve a file had listed for adding, only files already in the repo with modifications.

It is included with Mercurial as an "extension" which just means you have to enable it in your hg config file.


Notes for really old versions of Mercurial (before shelve was included -- this is no longer necessary):

I didn't see any great install instructions with some googling, so here is the combined stuff I used to get it working:

Get it with:

hg clone http://freehg.org/u/tksoh/hgshelve/ hgshelve

The only file (currently) in the project is the hgshelve.py file.

Modify your ~/.hgrc to add the shelve extension, pointing to where you cloned the repo:

[extensions] 
hgshelve=/Users/ted/Documents/workspace/hgshelve/hgshelve.py
Merola answered 24/9, 2008 at 3:41 Comment(0)
H
40

It's been almost 2 years since I originally posed this question. I'd do it differently now (as I mentioned in a comment above the question above). What I'd do now would be to instead commit my changes to the one file in my local repo (you can use the hg record extension to only commit pieces of a file):

hg commit -m "commit message" filename

Then just push out.

hg push

If there's a conflict because other changes have been made to the repo that I need to merge first, I'd update to the parent revision (seen with "hg parents -r ." if you don't know what it is), commit my other changes there so I've got 2 heads. Then revert back to the original single file commit and pull/merge the changes into that version. Then push out the changes with

hg push --rev .

To push out only the single file and the merge of that revision. Then you can merge the two heads you've got locally.

This way gets rid of the mq stuff and the potential for rejected hunks and keeps everything tracked by source control. You can also "hg strip" revisions off if you later decide you don't want them.

Helenahelene answered 13/7, 2010 at 17:33 Comment(2)
This sounds good. I start to go crosseyed whenever i see a letter 'q' anywhere near Mercurial. You may be amused by Steve Losh's 'hg nudge': hgtip.com/tips/advanced/2009-09-28-nudge-a-gentler-pushShylashylock
Using phases (in today's Mercurial) would make this even easier, there would be no need to specify --rev with push just to avoid pushing the wrong thing.Jubilee
M
31

There's a Mercurial feature that implements shelve and unshelve commands, which give you an interactive way to specify changes to store away until a later time: Shelve.

Then you can hg shelve and hg unshelve to temporarily store changes away. It lets you work at the "patch hunk" level to pick and choose the items to shelve away. It didn't appear to shelve a file had listed for adding, only files already in the repo with modifications.

It is included with Mercurial as an "extension" which just means you have to enable it in your hg config file.


Notes for really old versions of Mercurial (before shelve was included -- this is no longer necessary):

I didn't see any great install instructions with some googling, so here is the combined stuff I used to get it working:

Get it with:

hg clone http://freehg.org/u/tksoh/hgshelve/ hgshelve

The only file (currently) in the project is the hgshelve.py file.

Modify your ~/.hgrc to add the shelve extension, pointing to where you cloned the repo:

[extensions] 
hgshelve=/Users/ted/Documents/workspace/hgshelve/hgshelve.py
Merola answered 24/9, 2008 at 3:41 Comment(0)
F
10

tl;dr: My original explanation looks complicated, but I hope it fully explains how to use a patch queue. Here's the short version:

$ hg qnew -m "migration notes" -f migration my_migration.sql
$ hg qnew -f working-code
# make some changes to your code
$ hg qrefresh # update the patch with the changes you just made
$ hg qfinish -a # turn all the applied patches into normal hg commits

Mercurial Queues makes this sort of thing a breeze, and it makes more complex manipulation of changesets possible. It's worth learning.

In this situation first you'd probably want to save what's in your current directory before pulling down the changes:

# create a patch called migration containing your migration
$ hg qnew -m "migration notes" -f migration.patch my_migration.sql
$ hg qseries -v # the current state of the patch queue, A means applied
0 A migration.patch
$ hg qnew -f working-code.patch # put the rest of the code in a patch
$ hg qseries -v
0 A migration.patch
1 A working-code.patch

Now let's do some additional work on the working code. I'm going to keep doing qseries just to be explicit, but once you build up a mental model of patch queues, you won't have to keep looking at the list.

$ hg qtop # show the patch we're currently editing
working-code.patch
$ ...hack, hack, hack...
$ hg diff # show the changes that have not been incorporated into the patch
blah, blah
$ hg qrefresh # update the patch with the changes you just made
$ hg qdiff # show the top patch's diff

Because all your work is saved in the patch queue now, you can unapply those changes and restore them after you've pulled in the remote changes. Normally to unapply all patches, just do hg qpop -a. Just to show the effect upon the patch queue I'll pop them off one at a time.

$ hg qpop # unapply the top patch, U means unapplied
$ hg qseries -v
0 A migration.patch
1 U working-code.patch
$ hg qtop
migration.patch
$ hg qpop
$ hg qseries -v
0 U migration.patch
1 U working-code.patch

At this point, it's as if there are no changes in your directory. Do the hg fetch. Now you can push your patch queue changes back on, and merge them if there are any conflicts. This is conceptually somewhat similar to git's rebase.

$ hg qpush # put the first patch back on
$ hg qseries -v
0 A migration.patch
1 U working-code.patch
$ hg qfinish -a # turn all the applied patches into normal hg commits
$ hg qseries -v
0 U working-code.patch
$ hg out
migration.patch commit info... blah, blah
$ hg push # push out your changes

At this point, you've pushed out the migration while keeping your other local changes. Your other changes are in an patch in the queue. I do most of my personal development using a patch queue to help me structure my changes better. If you want to get rid of the patch queue and go back to a normal style you'll have to export your changes and reimport them in "normal" mercurial.

$ hg qpush
$ hg qseries -v
0 A working-code.patch
$ hg export qtip > temp.diff
$ rm -r .hg/patches # get rid of mq from the repository entirely
$ hg import --no-commit temp.diff # apply the changes to the working directory
$ rm temp.diff

I'm hugely addicted to patch queues for development and mq is one of the nicest implementations out there. The ability to craft several changes simultaneously really does improve how focused and clean your commits are. It takes a while to get used to, but it goes incredibly well with a DVCS workflow.

Frieder answered 20/2, 2009 at 12:39 Comment(3)
+1 for the explanation, but that looks very complicated. Compare it to "git rebase -i", which requires no thought or set up at all, and I still can't see how MQ is a workable solution to the "neat patch set" problem (for mortals!). It seems you have to really plan your workflow anyway.Chosen
I'm not sure I'd refer to a 20-step process of obscure commandlines as "a breeze."Hernardo
hehe, good call. I wrote the short, sweet answer to his solution up top.Frieder
L
3

Another option if you don't want to rely on extensions is to keep a clone of your upstream repository locally that you only use for these sorts of integration tasks.

In your example, you could simply pull/merge your change into the integration/upstream repository and push it directly up to the remote server.

Locale answered 10/10, 2008 at 1:35 Comment(0)
F
2

What i use generally is use to commit a single file :

 hg commit -m "commit message" filename

In case later, I have a merge conflict and I am still not ready to commit my changes,follow these steps:

1) Create a patch file.

hg diff > changes.patch

2) Revert all your outstanding uncommitted changes, only after checking your patch file.

hg revert --all

3) Pull,update and merge to latest revision

hg pull -u
hg merge
hg commit -m "local merge"

4) Now simply import your patch back and get your changes.

hg import --no-commit changes.patch

Remember to use -no-commit flag from auto committing the changes.

Factotum answered 3/2, 2015 at 22:0 Comment(0)
D
1

Since you said easiest, I often use hg commit -i (--interactive) even when committing whole files. With --interactive you can just select the file(s) you want rather than typing their entire path(s) on the command line. As an added bonus you can even selectively include/exclude chunks within the files.

And then just hg push to push that newly created commit.

I put more details on using hg commit --interactive in this answer: https://mcmap.net/q/166581/-mercurial-hg-commit-only-certain-files

Diarrhoea answered 21/12, 2017 at 19:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.