0

I've incidently switched to the branch develop from the branch analytics, where I had unsaved changes. Now I'm trying to switch back to the analytics branch and get the following error:

error: The following untracked working tree files would be overwritten by checkout:
application/db/some.sql
modules/analytics/config/database.php

How can I fix the issue without loosing data?

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488

3 Answers3

2

If the intent is to take the current changes and apply them to the branch analytics then you should do the following

git stash
git checkout analytics
git stash pop

The stash command will store the current branch changes and reset your working directory to HEAD. Once you've switched to the analytics branch you can reapply the changes by using stash pop. This will apply the most recent stash command to the current working directory

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • But for some reason Git shows among unsaved the files that I have committed to the `develop` branch before the checkout. Why does it do that? And some that I had committed to the `analytics` branch. That's why when I stashed, checked out to `analytics` and tried to pop changes, pop failed with messages `filename already exists, no checkout` – Max Koretskyi Jan 28 '14 at 17:28
  • This is the command I ran to checkout `git --work-tree=/path/to/outputdir checkout 'develop'` – Max Koretskyi Jan 28 '14 at 17:45
  • @Maximus it sounds like you have a file added in one branch but not another and hence stash is failing trying to pop an add of an existing file. If that's the case you may need to not include that file in the stash or pop attempt – JaredPar Jan 28 '14 at 17:53
2

The error message you see indicates that the files in question are untracked in your current branch, but tracked in the branch you want to switch to.

Here's an example, using just one file:

$ git checkout -b dev  # create new branch dev (I was on master)
Switched to a new branch 'dev'
$ echo contents > dev-file
$ git status
On branch dev
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    dev-file

nothing added to commit but untracked files present (use "git add" to track)
$ git add dev-file 
$ git commit -m 'dev-file is tracked in dev'
[dev 24e6171] dev-file is tracked in dev
 1 file changed, 1 insertion(+)
 create mode 100644 dev-file

So now there's a new file, dev-file, that is tracked in branch dev.

$ git checkout master # get back to situation without dev-file
Switched to branch 'master'
$ ls
LICENSE.txt x.txt

We see that dev-file does not exist here. Let's create it.

$ echo contents > dev-file
$ git checkout dev
error: The following untracked working tree files would be overwritten by checkout:
    dev-file
Please move or remove them before you can switch branches.
Aborting
$ 

The question becomes, what do you want to do about the file(s)?

In this case, we created dev-file with the same contents it would/will have, if/when we manage to git checkout dev. We can see that with git diff to compare:

$ git diff dev:dev-file dev-file
$ 

If we replace this untracked file with different contents (remember, we're still on master at this point), and try the same git diff, we'll see that they don't match:

$ echo different-contents > dev-file
$ git diff dev:dev-file dev-file
diff --git a/dev-file b/dev-file
index 12f00e9..462c93f 100644
--- a/dev-file
+++ b/dev-file
@@ -1 +1 @@
-contents
+different-contents
$ 

The question, though, is not really "do they match", but rather "what do you want to do about this situation" (in which dev-file is untracked in master, tracked in dev, and exists in the work directory while we are "on branch master").

You can:

  1. git add and git commit it (to make it tracked on the current branch, in this case master): it's now saved permanently via the new commit;
  2. remove the file entirely (while on branch master), so that git checkout dev will succeed, then git checkout dev;
  3. use git checkout -f dev to wipe out the file and get onto branch dev all at once (same as method 2);
  4. save the file somewhere other than a regular commit: e.g., use git stash -u as in aaronmallen's answer, or manually copy it somewhere outside the repository;
  5. invent some other solution. :-)

Methods 2 and 3 simply wipe out the version that is on your current branch, replacing it with the version that is on branch dev. If git diff says there's no difference between current-version and dev-version, you obviously have not lost any actual file contents in the process (since it's preserved in branch dev). If git diff says there is a difference, you will have lost the non-dev version.

In either case, though, if you switch back to master, the file will vanish:

$ git checkout -f dev
Switched to branch 'dev'
$ cat dev-file
contents

(It's back to the version in dev.)

$ git checkout master
Switched to branch 'master'
$ cat dev-file
cat: dev-file: No such file or directory

(It's gone. We can always retrieve the version in dev, but the different-contents we put in it earlier are gone.)

If you save the untracked files with git stash -u, you'll have a place from which to recover the untracked files. Using git stash -u like this will save a whole lot of stuff; see the documentation for git stash.

If you save the untracked files outside of git entirely, you'll have a (non-git) place from which to recover the untracked files.

If you commit the untracked files into the original branch, you'll have a place—in fact, the branch itself, here master—from which to recover the files, which are no longer untracked.

Which is the best solution? There's no one answer. Maybe they should be tracked. Maybe they should be "half tracked", as they are now. Maybe they should be untracked even in branch dev (which means you'll have to do some operations on branch dev to cause them to become untracked there: in particular, you'll have to check out that branch, use git rm --cached or git rm, and make a new commit in that branch). Git can't make this decision for you, nor can I; you need to figure out what you want to have happen with these files.


Edit: now that we know that the work directory got into this state via:

git --work-tree=/path/outside/here checkout develop

I can add a bit on this. I can't go into too much detail as I have not experimented with it much myself, nor gone through the index code in git with any kind of fine-toothed comb. But, one point of the index is to keep track of what's in your work tree.

It's very expensive, at least in a large project with a lot of code in a lot of files in a lot of directories, to search the entire work tree—but with some cleverness, git does not have to do that. Let's assume your work tree starts at ., and you have modules/analytics/config/* for instance. Inside the index (.git/index), there are entries keeping track of stat information about the files in that directory, as checked-out.

Git also knows that you're (as git status would report) on branch analytics. Then you invoke git checkout develop.

Rather than searching the work directory for "what's there", git can use the data in the index to assume that "what's there" is what was last checked-out. In some (I'm not sure which) cases, git will re-do the stat and know the index is out of date. In others (again I'm not sure which), I can see by observation that it does not do that: it just assumes that .git/index describes the work tree.

If you always use the same work tree, with that (single) index, it "just works". If you use --work-tree to point git to a different work tree, I've seen it do what amounts to: "ah, well, to switch from analytics to develop I only need to remove this file and replace it with that other one"—so that's all it does.

You can force git to use a different index file with GIT_INDEX_FILE=path. For instance, if you wanted to track what goes into /path/outside/here you could have a file named (e.g.) .git/index-alt:

# note: this assumes $GIT_DIR is set, as it is with "git-sh-setup"
# so let's set it:
GIT_DIR=$(git rev-parse --git-dir) || exit

alt_index=${GIT_INDEX_FILE-"$GIT_DIR/index"}-alt
GIT_INDEX_FILE=$alt_index git --work-tree=/path/outside/here git checkout ...

This still has one other problem, that git checkout ... will switch your current branch. To fix that, use an alternate form of git checkout, telling it to check out all the files from the named branch, but without changing your current branch:

GIT_INDEX_FILE=$alt_index git --work-tree=/path/outside/here checkout branchname -- .

Now the "alternate index" tracks the alternate work tree, and HEAD is never changed, so the local repository is never messed-with. (I have not tested this, but should work—it's something I've been planning to play with as part of "how best to deploy versions on servers using post-receive hooks".)

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks for you awesome answer! Let me read it thoroughly and get back with questions :) – Max Koretskyi Jan 28 '14 at 18:14
  • I've read everything. Thank you once again for elaborate explanation. And now I have a question - you created `dev-file` when switched to `master` after making the commit on `dev` branch, while I didn't create them. How did they get to the `master` branch? – Max Koretskyi Jan 28 '14 at 18:31
  • My branch names are different, you have `analytics` and `develop` and are "on" `develop` (per your question). Something you did while, or in switching to, "on `develop`" has left them around as untracked files, while something you or someone else did earlier has committed them in `analytics` as tracked files. But I don't know what those something(s) are. [The .sql file was most likely ceated by running a command that writes .sql files; no idea about the .php file.] – torek Jan 28 '14 at 18:35
  • I see, thanks. I ran this command `git --work-tree=/path/to/outputdir checkout 'develop'` when working on `analytics` which switched me to the `develop` branch. It'd be nice to know what caused those files to appear untracked on `develop`. Aside question, why did you put quotes around `on` preposition? :) – Max Koretskyi Jan 28 '14 at 18:42
  • 1
    Aha! The `--work-tree=` dropped the work files elsewhere (so did not touch your work dir), but also changed the notion of "current branch" in the `.git` dir. So one easy trick to get back is: `git --work-tree=/path/to/temp/dir checkout analytics` which will also leave your work dir untouched. The temp dir can then be removed. (And re aside: "on xxx" is an approximate quote from `git status`, which would say `on branch xxx`, hence the quotes.) – torek Jan 28 '14 at 19:16
  • _git --work-tree=/path/to/temp/dir checkout analytics_ - excellent solution, it's a pity that I've already used another solution with deleting the files. It seems though that I haven't lost anything. Interestingly, `git --work-tree=/path/to/temp/dir checkout analytics` checked out only 3 files and 2 folders instead of full working tree. Any idea why that happened? – Max Koretskyi Jan 28 '14 at 19:49
  • 1
    Yes: git keeps track, in the index/staging area, of what is currently checked-out. When you use `--work-tree` it still trusts (and updates) your index (`.git/index`) unless you point *that* elsewhere too. Basically, it comes down to "don't use `--work-tree` carelessly, it has surprising traps in it". :-) – torek Jan 28 '14 at 19:54
  • _ Basically, it comes down to "don't use --work-tree carelessly, it has surprising traps in it_ - I've seen that already :). I'd appreciate if you elaborated a bit on the _When you use --work-tree it still trusts (and updates) your index (.git/index) unless you point that elsewhere too._ regarding the behavior I've seen as I want to avoid using `--work-tree` carelessly. – Max Koretskyi Jan 28 '14 at 20:14
  • @Maximus: I added some stuff on using `GIT_INDEX_FILE`. – torek Jan 28 '14 at 23:27
  • Fantastic explanation! Thank you very much! It's a pity I can upvote only one time. This explains why when I checkout a branch to some other folder on my production server where the repository is bare the all files being exported - because index shows that there are currently no files in the work tree, right? – Max Koretskyi Jan 29 '14 at 12:22
  • I'm still not 100% sure when git decides that the index is stale and ignores/rebuilds it, and when it trusts it. What I have found is that if you have a bare repo that uses `--work-tree` to deploy files, and you deploy different branches to different trees, if you let git use just one index file, it screws up sometimes, especially if it needs to *remove* some files from some exports. – torek Jan 29 '14 at 13:47
  • Got you, thanks! Actually, I just noticed that I'm using your advices from [here](http://stackoverflow.com/a/19928823/2545680) to deploy to production :). It's one branch being deployed only so I guess one index file is fine. However, if I want to deploy from several branches to several different folders, I might need several git index files, correct? – Max Koretskyi Jan 29 '14 at 14:20
  • 1
    Right. (I haven't been doing any deployment stuff with git in a long time so I have not experimented to figure out a Best Way To Do It.) – torek Jan 29 '14 at 14:35
  • I don't. Documentation on the git index file is a pretty thin: the best seems to be that in git itself (`Documentation/technical/index-format.txt`). – torek Jan 29 '14 at 16:47
1

You can use git stash git stash -u. Git Stash

aaronmallen
  • 1,418
  • 1
  • 12
  • 29