ChatGPT解决这个技术问题 Extra ChatGPT

Temporarily put away uncommitted changes in Subversion (a la "git-stash")

While programming software stored in a Subversion repo, I often modify some files, then notice that I'd like to do some preparatory change for my main work. E.g. while implementing new functionality, I notice some refactoring which might help me.

In order not to mix two unrelated changes, in these cases I'd like to "stow away" my changes, i.e. revert to the repository version, do some other changes, commit these, then "fetch back" my changes.

git-stash allows to do just that. Is there some way to do this with Subversion, either directly or with some plugin or script. Eclipse plugins would also be fine.

just curious, but why not use git-svn?
Some relevant news: infoworld.com/d/application-development/… (quoting: "He also notes that the upcoming Subversion 1.8 release should bring it closer to Git's capabilities, with features like Git stash, in which a developer can make changes locally and then set them aside, and offline commits, which records completed changes when a developer is offline and moves the to the master repository when the developer reconnects."
Update (as of 2012-04-26): Shelving is now scheduled for 1.9, without any ETA. So it may take a while...
Update (as of 2012-11-17): Shelving is now scheduled for 1.10. Maybe it is always scheduled for ? ;-)
Update (as of 2015-03-23, 2 years and half later): Good news is that Shelving is still scheduled for 1.10. Bad news are the ETA: Q2 2015 (tentative) Release 1.9.0 / 2017? (speculative at best) Release 1.10.0 (subversion.apache.org/roadmap.html)

W
Walter Mundt

This blog post advises using diff and patch.

git stash approximately becomes svn diff > patch_name.patch; svn revert -R .

git stash apply becomes patch -p0 < patch_name.patch

Note that this doesn't stash metadata changes or (I think) directory creates/deletes. (Yes, svn tracks those separately from directory contents, unlike git.)


This is an accidental duplicate of stackoverflow.com/questions/1554278/… -- send upvotes there.
It also doesn't seem to include binary files, which is annoying. At least when using TortoiseSVN to generate the patch.
You can more or less have the metadata tracked if you use svn patch patch_name.patch instead of patch -p0, because they're in the patch file, and svn patch understands them.
This doesn't include changes to externals.
L
Lii

You can store your current changes with svn diff into a patch file, then revert your working copy:

svn diff > stash.patch
svn revert -R .

After you’ve implemented your preparatory feature, you can then apply your patch with the patch utility:

patch < stash.patch

As others have noted this will not work with svn:properties and tree operations (add, remove, rename files and directories).

Binary files could also give problems, I don’t know how patch (or TortoiseSVN in this case handles them).


This probably doesn't work too well with removed/renamed files, I think.
See the box titled "Why Not Use Patches Instead?" at svnbook.red-bean.com/en/1.5/… to understand why this is a bad idea.
@sbi: I don't think that's a valid justification for a downvote. It's not a "bad answer". It's just not the perfect answer that's all. I don't think this person deserves punishment for his suggestion. Would you prefer him not to answer instead? If yes, then yes, you should downvote. Otherwise this is punishing good intentions.
in case anyone else, like me, thought this looks like the lightest-weight solution and decides to try it, I had to use patch -p0 < stash.patch - otherwise it complained about not being able to find the files to patch
This advice helps especially if you are coming from a git background and are forced to use SVN due to various reasons. A small improvement in the advice already given for first time users of patch: $ patch --strip=0 < stash.patch This will make sure that patch does not ask you the file name when you are applying your patch.
L
Lii

When I've got uncommitted changes from one task in my working copy and I need to switch to another task, I do one of two things:

Check out a new working copy for the second task. or Start a branch: workingcopy$ svn copy CURRENT_URL_OF_WORKING_COPY SOME_BRANCH workingcopy$ svn switch SOME_BRANCH workingcopy$ svn commit -m "work in progress" workingcoyp$ svn switch WHATEVER_I_WAS_WORKING_ON_BEFORE

I have some scripts that help to automate this.


this will result in a lot of trash on your subversion server
@knittl: No, it won't. And what's even more important: It won't result in changes lost as does your suggestion. This, and having another checked out copy of the trunk/same branch, are the only two reliable ways to do this that I know. If you feel uncomfortable with this, just check out another copy and work on it in parallel.
@knittl: the branch can be created in an inconspicuous path that is outside the project's default branches or tags location. For example, a team can designate project\temp\<creationdate-reason> or project\personal\<creationdate-reason> for this purpose.
It's still unfortunate that the branch has to be created on the server at all. It's not that such branches duplicate lots of data, but that they create a lot of unnecessary references that a system like git does without.
this is not useful with a large repository. This is absolutely not an option in my work environment. And while I wish our repository were smaller and better organized, and quite frankly, a git repository instead of svn, i'm confined to the limits of how our code is organized in our organization.
J
JesperE

The easiest way would be to use a temporary branch, like this:

$ svn copy ^/trunk ^/branches/tempbranch
$ svn switch ^/branches/tempbranch
$ svn commit -m "Stashed"
$ svn switch ^/trunk
$ ... hack away in trunk ...
$ svn commit -m "..."
$ svn merge ^/branches/tempbranch .
$ svn rm ^/branches/tempbranch
$ ... continue hacking

This could (and probably should) be put in a script if done on a more regular basis.


Why is this voted down, while "solutions" are voted up that don't even work when you have deleted/added files or have changed any properties? Yes, this isn't the easiest thing to do when you do it for the first time, but, besides having another copy checked out to work in parallel, this is the only solution that works in all cases.
Nice use of ^ syntax for repo root (since svn 1.6). Good solution when your repo has trunk/tags/branches at top level.
I don't really like putting all these temporary branches on the server. I feel this should be done locally, instead of cluttering up the server (and generating spurios checkin emails, if you generate mails on checkin). Still, an option worth remembering.
@sleske: yes, you are committing your temporary stash to the server, but the branch itself is deleted. Anyway, I think this is the fastest and most robust way to do it.
@sleske: SVN is not a distributed VCS, so everything has to be on the server. That's just the way it is.
s
snipsnipsnip

As of 1.10.0 (2018-04-13), you have experimental svn shelve command. (TortoiseSVN supports the command) It's nothing but a helper to save a patch and apply back, so it has same limitations as svn diff + patch (i.e. can't handle binary files and renames). (Edit: Looks like binary support is coming at next version 1.11.0)

Edit^2: With 1.11.0 (released 2018-10-30), binary files are supported. Shelving renamed files remained unsupported. Shelving in 1.11 is incompatible with shelves created by 1.10.

Edit^3: With 1.12.0 (released 2019-04-24), Copying and renaming are supported. Shelving in 1.12 is incompatible with shelves created by earlier versions.

Edit^4: There is no change around shelving with 1.13.0 (Oct 2019) and 1.14.0 (May 2020). Commands are still marked as experimental and you need to define SVN_EXPERIMENTAL_COMMANDS=shelf3 to enable the feature. It looks like the feature is currently untriaged.

Design notes can be found at developers' Wiki.

$ svn x-shelve --help
x-shelve: Move local changes onto a shelf.
usage: x-shelve [--keep-local] SHELF [PATH...]

  Save the local changes in the given PATHs to a new or existing SHELF.
  Revert those changes from the WC unless '--keep-local' is given.
  The shelf's log message can be set with -m, -F, etc.

  'svn shelve --keep-local' is the same as 'svn shelf-save'.

  The kinds of change you can shelve are committable changes to files and
  properties, except the following kinds which are not yet supported:
     * copies and moves
     * mkdir and rmdir
  Uncommittable states such as conflicts, unversioned and missing cannot
  be shelved.

  To bring back shelved changes, use 'svn unshelve SHELF'.

  Shelves are currently stored under <WC>/.svn/experimental/shelves/ .
  (In Subversion 1.10, shelves were stored under <WC>/.svn/shelves/ as
  patch files. To recover a shelf created by 1.10, either use a 1.10
  client to find and unshelve it, or find the patch file and use any
  1.10 or later 'svn patch' to apply it.)

  The shelving feature is EXPERIMENTAL. This command is likely to change
  in the next release, and there is no promise of backward compatibility.

Valid options:
  -q [--quiet]             : print nothing, or only summary information
  --dry-run                : try operation but make no changes
  --keep-local             : keep path in working copy

(...)

$ svn x-unshelve --help
x-unshelve: Copy shelved changes back into the WC.
usage: x-unshelve [--drop] [SHELF [VERSION]]

  Apply the changes stored in SHELF to the working copy.
  SHELF defaults to the newest shelf.

  Apply the newest version of the shelf, by default. If VERSION is
  specified, apply that version and discard all versions newer than that.
  In any case, retain the unshelved version and versions older than that
  (unless --drop is specified).

  With --drop, delete the entire shelf (like 'svn shelf-drop') after
  successfully unshelving with no conflicts.

  The working files involved should be in a clean, unmodified state
  before using this command. To roll back to an older version of the
  shelf, first ensure any current working changes are removed, such as
  by shelving or reverting them, and then unshelve the desired version.

  Unshelve normally refuses to apply any changes if any path involved is
  already modified (or has any other abnormal status) in the WC. With
  --force, it does not check and may error out and/or produce partial or
  unexpected results.

  The shelving feature is EXPERIMENTAL. This command is likely to change
  in the next release, and there is no promise of backward compatibility.

Valid options:
  --drop                   : drop shelf after successful unshelve
(...)

$ svn help | grep x-
 x-shelf-diff
 x-shelf-drop
 x-shelf-list (x-shelves)
 x-shelf-list-by-paths
 x-shelf-log
 x-shelf-save
 x-shelve
 x-unshelve

It would be better if you can describe shortly how shelve works instead of putting all command line docs here. shelve is the best solution I think. I would like to write an summary answer comparing all the solutions mentioned above. Those answers are more or less incomplete.
W
Walter Mundt

I don't know of an easy way to do that with just svn. Honestly, I'd advise using git-svn to make a git repo that acts as an svn working copy, and just using git stash with that. Just replace git pull with git svn rebase and git push with git svn dcommit and you can actually keep 90% of your git workflow and still be talking to an svn server.


But the link stackoverflow.com/questions/1554278/… I mention in the comments above does propose practical solution to do a stash in svn only.
Fair enough; in fact, google lead me to that solution on a blog just now. I still maintain that, for this questioner, git-svn is a natural solution.
I doubt that solution follows the file renames, since git doesn't.
c
cxxl

There is a small Python 2 script called svn-stash available under GPL 3: https://github.com/frankcortes/svn-stash .

It works like the svn diff/patch solutions mentioned and offers pushing and popping of changes as diffs into some local directory. Unfortunately, the stashes can not be named, and only the last one can be popped (well, yeah, it's a stack, but there is no real reason for such a limitation.) But then, you could always build the missing features into the source.

It is written for *ix, but after replacing every "/" with os.sep it works nicely under Windows as well.

If you use svn 1.7 or higher, you need to change is_a_current_stash(): remove the line if ".svn" in os.listdir(CURRENT_DIR):, since there is only one top-level .svn subdir in 1.7 WC's.


It doesn't for me under windows! :(
l
lili

You can do it easily using Intellij IDEA - Shelve Changes


Does this way can handle metadata changes and directory creates/deletes? Like exactly what git stash does?
k
knittl

another option is to copy your current checkout to a new directory and revert all your changes. this way you’ll save the hassle of creating a temporary branch on your server—after all stashing is a local operation, which not everybody should see and can be done quite often.

after committing your hotfix you can update your main working copy and delete your “stashing area”


Note: That's essentially the same as checking out a second working copy - only without the checkout :-).
@sleske: yes, without the huge amount of bandwidth necessary for a new checkout
Like it or not, this is the answer which more closely mirrors "git stash" behavior. Creating a branch IS cool, but is more related to TFS shelving.
a
angularsen

I always keep a second checkout, which I call "trunk_clean". Whenever I need to do a quick, isolated change related to what I am doing, I just commit on that checkout instead.


A
Anthony Shaw

I have also wanted this feature. I currently use TortoiseSVN.

I have not found a hardfast solution except to export the tree, revert back to repository make my changes and commit, then compare the changes from the exported tree back into my source controlled directory using a tool like Beyond Compare.

Or, another solution might be to branch from the HEAD to another directory, make your changes and the commit. Once you're ready to merge those back to your other working copy, do an update and merge your changes.


R
Ryan DeBeasi

The branching and patching ideas above are great, but they don't work well for me. I use a visual diff tool, so running git diff doesn't produce text-based patches. Our build system spins up a new environment each time a branch is created, so creating temporary "stash" branches would get messy.

Instead, I wrote a little shell script that copies a file to a "shelf" directory, adds a timestamp, and reverts the change. It's not as robust as the solutions above, but it also avoids some of the pitfalls that I ran into.


R
Raheel

Based on the Walter's answer I have created the following aliases in my bashrc file:

alias svn.stash='read -p "saving local changes in raq.patch. Existing stash in raq.patch will be overwritten. Continue?[y/N]" && [[ $REPLY =~ ^[yY] ]] && rm -f raq.patch && svn diff > raq.patch && svn revert -R .'
alias svn.stash.apply='patch -p0 < raq.patch; rm -f raq.patch'

These aliases are much easier to use and remember.

Usage:

svn.stash to stash changes and svn.stash.apply to apply stash.


L
Lii

In my practice, I use git init to create a Git repository in trunk directory of my Subversion repository, and then I add *.git to the Suctions ignore patterns.

After modifying some files, if I want to continue my work with the Subversion mainline, I just use git stash to stash my work. After committing to the Subversion repository, I use git stash pop to restore my modifications.


This is actually a good solution! Many other solutions use 3rd party tools to solve the problem; this one uses Git as a 3rd party tool. This has several advantages: 1) Git is very general and powerful. 2) Many people already have Git installed.
I'm curious how this works if you don't also do a git commit.
a
anton_rh

Use:

svn cp --parents . ^/trash-stash/my-stash

It will create a branch from the current location and current revision, and then it will commit the changes in working copy to that branch without switching to it.

usage: copy SRC[@REV]... DST SRC and DST can each be either a working copy (WC) path or URL: WC -> URL: immediately commit a copy of WC to URL

Note that changes in working copy won't be automatically reverted (cp is just CoPying changes to a new branch) and you have to revert them manually.

To restore the changes, you can just merge the changes from newly created branch to your working copy.

svn merge --ignore-ancestry ^/trash-stash/my-stash -c <commited revision>

--ignore-ancestry is used in order not to update merge info in working copy.

Use:

svn ls -v ^/trash-stash/

to see what you have at stash path. Committed revisions are also printed.

If you don't need the stash anymore, just run:

svn rm ^/trash-stash/my-stash

This solution is better than using patch in that if new changes in working copy or on the current branch conflict with the changes in the stash, you can resolve the conflicts using svn means, whereas patch in some cases will just fail or even apply patch incorrectly.


R
Rick

I would like to make a summary for all the solutions mentioned above, since it's such a mess under this question. Some high voted answers are ambiguous and I spent quite a lot time proving whether some part of the answer is true or not.

Solutions:

Checking out a new working copy and work in the new copy. (The easiest and safest one) Create a branch -> switch to new branch -> blablabla (Some say it will produce some trash in the SVN server) Create a patch -> revert working copy -> patch back (Works great if you don't have any unadded files or deleted files) Use shelve (See below)

I tried 1. 2. and 3..

1. is the easiest and safest one. If you want to save time, use this solution. Not elegant I know.

3. is not my choice because:

you can create a patch with unadded files and changes of existing files. But it doesn't delete those unadded files after creating a patch. So what to do? I have to creat a patch(select unadded files) -> revert working copy -> manually delete all those unadded files. This doesn't work like git stash -u at all.

4. shelve would be the most elegant way and the most similar one to git stash -u.

add unadded/untracked files -> shelve -> done.

See? Compared to git stash -u, the only difference is that you have to add the unadded file first then shelve.

Test Environment:

I am testing all those using Windows Tortoise SVN client with a network sharing copy (SAMBA) and local repos created by Windows Tortoise SVN client.

So I don't know how things could be different if you are using a SVN server, which is different from a local share. But I guess shelve would work in any siutaions since it's a local operation/feature.


w
wonsuc

Since Subversion doesn't support stash feature perfectly,
I just do manual way like this.

Place Development and Production(release) project to a separated path.

source\code\MyApp         -- Development
release\MyApp(release)    -- Production(release)

You can work any new features for your project in the development path, and you would only commit meaningful progress or something should be released for the stable.

When you have to release it for production, open production project, update svn and do stuff to release(build, export... etc).

I know this makes little bit troublesome, but releasing progress doesn't happen often(it doesn't for me, but I know some projects do) compare to develop progress, this way fits for me.

I'm using svn for specific projects since the project team members use it, so I have to follow.
The best solution is to use git which has a perfect version control system and better than svn.


It's not quite clear what you are doing (what version is checked out in the directories you mention?), but it looks like a duplicate of the top-voted answer ("Check out a new working copy").
@sleske Sorry, I didn't read your case details. In my case, I only need dev and prod, 2 situations. To develop completely new functionality would be complicated with svn. I'm not sure if there's clear method to solve your case in svn world.

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now