virendrao
virendrao

Reputation: 1813

How do i add .gitignore to all branches in repositories in Git?

I have more than 5 branches in my repository. I want to add .gitignore file to each branch and push it to remote repository.

I tried running following commands one by one after cloning remote repository.

Commands i ran in sequence:

 for branch in $(git branch --all | grep '^\s*remotes' | egrep --invert-match '(:?HEAD|master)$'); do    git branch --track "${branch##*/}" "$branch"; done

for branch in $(git branch); do    git checkout $branch && touch .gitignore; done

for branch in $(git branch); do    git checkout $branch && git status; done

for branch in $(git branch); do    git checkout $branch && echo ".classpath" >> .gitignore && echo ".project" >> .gitignore && echo ".settings/" >> .gitignore && echo "log/" >> .gitignore && echo "target/" >> .gitignore ; done

for branch in $(git branch); do    git checkout $branch && git add .gitignore; done

for branch in $(git branch); do    git checkout $branch && git commit -m 'Adding gitignore'; done

for branch in $(git branch); do    git checkout $branch && git push -u origin $branch; done

Dont know but it doesnt perform as expected.

Please help.

Upvotes: 1

Views: 2016

Answers (2)

virendrao
virendrao

Reputation: 1813

I have resolved as per @torek suggestion, you can following script as follows to add .gitignore file:

#! /bin/sh -e
# set -x  # for debug
[ -d mydir] && { echo "error: mydir directory already exists"; exit 1; }
git clone <git-repo-url> mydir
cd mydir
for name in $(git for-each-ref --format='%(refname:short)' refs/remotes/origin); do
     [ "$name" = origin/HEAD ] && continue
     branch=${name#origin/}
     git checkout $branch
     echo ".classpath" >> .gitignore && echo ".project" >> .gitignore && echo ".settings/" >> .gitignore && echo "log/" >> .gitignore && echo "target/" >> .gitignore
     git add .gitignore
     git commit -m 'adding gitignore'
done

Once above is done, push code using following command:

git push origin 'refs/heads/*:refs/heads/*'

Upvotes: 0

torek
torek

Reputation: 489073

First, git branch is meant to be user-friendly, not programming-friendly. Don't use it for programming: use the programming-friendly "plumbing" command, git for-each-ref.

Second, as Leon said in a comment, write your script—a one-off script is a good idea, it's much easier to write and use repeatedly, if needed, on new (fresh, clean) clones, until you get it right—with one loop that does everything necessary inside the loop.

Last, don't do the git push here at all. Do it separately. Git has a built in way to push "all branches".

Your first loop reads (with my formatting):

 for branch in $(git branch --all | grep '^\s*remotes' |
         egrep --invert-match '(:?HEAD|master)$'); do 
     git branch --track "${branch##*/}" "$branch"
 done

This looks like you want to take your existing remote-tracking branches (origin/* or whatever) and create new local branches from them, with the new local branches set to have the corresponding remote-tracking branch as their upstreams. You exclude master here for some reason (I think I know the reason), and you also exclude the annoying symbolic HEAD ref that shows up as:

  remotes/origin/HEAD -> origin/master

Your remaining loops do not exclude master (but also fail to handle the * that git branch prints, so they are going to produce some error messages at least). Thus, it looks like you want to do this action on master too. That means you are probably skipping it in the first loop just because it already exists, which is usually true1 in a fresh clone.

So, let's write something that we expect to run on a fresh git clone, i.e., one that has no branches other than master. (Though, see footnote 1 again.) We'll try to fix other bugs along the way as well.

There is one minor issue: we will assume here that the name of the remote is origin, so that the remote-tracking branches have names starting with refs/remotes/origin/. This makes it easier to loop over all the names and strip off the origin/ prefix. We'll start our script, then, with this:

for name in $(git for-each-ref --format='%(refname:short)' refs/remotes/origin); do

This will iterate over all the shortened names: origin/HEAD, origin/master, and so on.

Next, we want to skip those that are in fact symbolic references like HEAD. We could just skip the literal HEAD, which is a lot simpler. The fancy way is to use git symbolic-ref to test it, but there is no point, the only actual one we'll see (for historical reasons) is HEAD, so let's just do this:

    [ "$name" = origin/HEAD ] && continue

Now what we need to do is create and check out a local branch that has the corresponding remote-tracking branch set as its upstream. We can do this in one easy step using git checkout, which has that built-in.

This also lets us avoid special-casing master: git clone already ran git checkout master, which created master and set it up with origin/master as its upstream, but we will need to use git checkout master anyway. This will check out the existing master that git clone created (when git clone ran git checkout master). So our next step is to construct the correct branch name:

    branch=${name#origin/}
    git checkout $branch

We only want to strip origin/, not "everything up to the last slash", in case one of the remote-tracking branches is named origin/feature/life and another is named origin/feature/zorg, for instance. We'll need local feature/life from which we can update origin/feature/life.

If the branch is master, it already exists, and this checks it out. If the branch is anything else, it does not exist, but since this is a fresh clone, only one remote-tracking branch does exist—the one with $name—so we'll create the local branch and check it out, and it will have the remote-tracking branch set as its upstream.

Now we want to create a .gitignore, put a bunch of stuff in it, add it, and commit it. One important question is whether a .gitignore already exists in the commit we just checked-out, and if so, what we want to do about it. I can't answer that question for you. Another is whether any of the files we are about to add to this .gitignore exist in the commit we just checked-out, and if so, what to do about that. Again, I can't answer this. I will note, however, that touch .gitignore followed by git status will only print something if .gitignore is newly created by touch. It might be more sensible to test directly:

    if [ -f .gitignore ]; then
        ... do whatever should be done here ...
    else
        ... create it ...
    fi

If "whatever should be done" is "nothing special", we have it easier, as we can simply add to it using >>, which will create it if it does not exist, or add to if it does exist.

Rather than five separate echo ... >> .gitignore, we can use one cat >> .gitignore and a "here-document":

    cat << END >> .gitignore
.classpath
.project
.settings
log/
target/
END

Now we git add and git commit as usual, and that is the end of our loop:

    git add .gitignore
    git commit -m 'add gitignore'
done

Write all of this into a shell script file (/tmp/doit.sh or /tmp/t.sh are my favorite names for such scripts). Add a #! /bin/sh -e line or set -e at the top and make the file executable—the -e ensures that the script will exit if any of the commands fails for any reason. (Add set -x for debug as well.) Add an explicit git clone line before the loop, so that it clones the repository, so that we are sure to have a fresh clone. Remember to change directory into the clone as well.

The rest of this example puts the new repo into a directory named "newrepo". It might be better to make a temporary directory with mktemp -d.

#! /bin/sh -e
# set -x  # for debug
[ -d newrepo ] && { echo "error: newrepo directory already exists"; exit 1; }
git clone scheme://example.com/path/to/repo.git newrepo
cd newrepo
for name in ... # see above for the rest

Now you can run /tmp/doit.sh and make sure everything worked. If so, continue; if not, fix the script and start over.

Once everything does work, we're ready to git push:

$ cd newrepo
$ git push origin 'refs/heads/*:refs/heads/*'

This last step is a push with a refspec, and the refspec here says "for each of my branches, do a polite (non-forced) push to origin using the same name on origin". Since we made sure to have our branches have the same names—such as feature/life—as those on origin, this is just what we want.

(Note: the above is all entirely untested. Beware typos and the like.)

Upvotes: 2

Related Questions